Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Johnathan Hunt
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Johnathan Hunt

Adding An Angular 14 Front-End To My ColdFusion Feature Flag Exploration

By
Published in , Comments (3)

About a month ago, I posted Strangler: Building a Feature Flag System in ColdFusion. That proof-of-concept was constructed in Lucee CFML using a standard post-back workflow wherein each navigation begot a full page refresh. Over the last few weeks, I've been dribbling some effort into creating a thick-client experience using Angular 14. The UI (User Interface) still leaves a lot to be desired; but, I think as a second-stage proof-of-concept, there's enough here to be demoed.

View this code in my Strangler project on GitHub.

It's been quite a while since I've built anything of any complexity in modern Angular (my day-to-day work still involves AngularJS 1.7). And, seeing as this Angular UI includes routing, services, and API calls, it ended up having a lot of moving parts despite its relatively small size and scope. Needless to say, I am feeling rather rusty when it comes to modern front-end architecture.

There's way too much code in this Angular app to show in a single post; so, I think maybe the only part that I'll share outside of the GitHub repo is the TypeScript definition for the Feature Flag data-model. Having not really touched TypeScript in a while, thinking in terms of Interfaces entailed a bit of trial-and-error, writing some TypeScript code and then seeing if it would compile.

The complexity with the Feature Flag data model is that it is not uniform. There are several different types of flags (Boolean, Numeric, String, Any). And, there are several different types of operators (User-key, User-property). And, there are several different types of distribution models (Single, Multi).

I ended up using a healthy amount of Discriminating Unions, which allow a dynamic portion of a Type-tree to change based on the existence of a static, known value. So, for example, to create the various types of feature flags, I first created a BaseFeatureFlag followed by several members of a discriminating union that extend the BaseFeatureFlag. Then, the top-level feature flag is a basic Type union of the lower-level feature flags:

export namespace FeatureFlags {

	interface BaseFeatureFlag {
		key: string;
		name: string;
		description: string;
		rules: Rule[];
		fallthroughVariantRef: number;
		isEnabled: boolean;
	}

	export interface AnyFeatureFlag extends BaseFeatureFlag {
		type: "Any";
		variants: any[];
	}

	export interface BooleanFeatureFlag extends BaseFeatureFlag {
		type: "Boolean";
		variants: boolean[];
	}

	export interface NumericFeatureFlag extends BaseFeatureFlag {
		type: "Numeric";
		variants: number[];
	}

	export interface StringFeatureFlag extends BaseFeatureFlag {
		type: "String";
		variants: string[];
	}

	export type FeatureFlag =
		| AnyFeatureFlag
		| BooleanFeatureFlag
		| NumericFeatureFlag
		| StringFeatureFlag
	;

	// ... truncated ....
}

Here, the type property is the discriminator which is what allows the variants property to have a different type for each feature flag. Then, you can see that the top-level FeatureFlag is just a union of all four discriminated types.

I used this same approach for the different types of Test configurations:

export namespace FeatureFlags {

	// ... truncated ....

	export interface UserKeyTest {
		type: "UserKey";
		operation: Operation;
	}

	export interface UserPropertyTest {
		type: "UserProperty";
		userProperty: string;
		operation: Operation;
	}

	export type Test =
		| UserKeyTest
		| UserPropertyTest
	;

	// ... truncated ....
}

And with the distribution models as well:

export namespace FeatureFlags {

	// ... truncated ....

	export interface SingleRollout {
		type: "Single";
		variantRef: number;
	}

	export interface MultiRollout {
		type: "Multi";
		distribution: Distribution[];
	}

	export type Rollout =
		| SingleRollout
		| MultiRollout
	;

	// ... truncated ....
}

Once I had these discriminated unions in place, I could start dynamically changing parts of this complex feature flag data model and the TypeScript compiler was happy to oblige.

There's likely a lot of code in this Angular 14 app that people won't agree with. I prefer Promises over RxJS Steams; and, I prefer letting my Components load their own data over performing data-loading in route-guards. But, I believe at the end of the day, this code is - at least - fairly easy to reason about. If nothing else, it felt good to tip my toes back in the Angular pool.

Want to use code from this post? Check out the license.

Reader Comments

15,848 Comments

After publishing this Angular 14 UI, I kept feeling like something was amiss in my error handling. As such, I went back and added Type Guards and Type Narrowing into my error handling workflow:

www.bennadel.com/blog/4324-using-type-guards-to-narrow-down-error-handling-types-in-angular-14.htm

This allows me to narrow down my error types from the default any to the ErrorResponse interface being returned by my ApiClient. This is what I love about TypeScript - that is forces me to think more deeply about the code. That's the real value, right there!

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel