Select Page

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

Cyberdime
Published: September 17, 2022

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.

Source: www.bennadel.com