Adding An Angular 14 Front-End To My ColdFusion Feature Flag Exploration
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
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 theErrorResponse
interface being returned by myApiClient
. 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!New to see your blog
@Aamir,
Hopefully I can produce some writing that interests you 🙌
Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →