Feature Flags in Angular

Posted on September 08, 2020

Written by Preston Lamb

angular

tldr;

Many times when working on applications, especially with teams, multiple features are being worked on at the same time. They aren’t all ready for deploy at the same time, though. At first blush, it seems like the only option is to hold off on deploys until everything is ready to go live, but there’s no telling how long that could take. Also, in the meantime more features are added to the codebase. It’s a never-ending cycle. But with feature flags, you can turn things off and on in different environments whenever you’re ready. You can deploy to production at any time and just turn the feature flag off for a given part of the app. In this post, we’ll go over how to add feature flags to your app. We’ll look at a service that can be used in class files, a directive that you can add to HTML elements, and a guard to protect new routes in the application. By the end, you should have a fully functioning feature flag service.

Feature Flags

Before we get started, let’s make sure we know what feature flags are, at least in this context. When I speak about feature flags, I mean a true/false value that can be set in a configuration file that turns a feature on or off. In this case, it’s either on for everyone or off for everyone; this isn’t A/B testing where the flag randomly decides to be on for some visitors to the site and off for others (although you could use these same services for that functionality if you wanted). The purpose of feature flags is to be able to deploy to a given environment, whether it’s a test or staging or production environment, even if a feature is not ready yet. The flag is turned off, though, so nobody knows that the feature exists. Once the feature is ready, you turn the flag on and everything is good to go.

Not ready could mean that there are bugs still being worked out, but it could also mean that you have a Black Friday sale or a special product launch that you want to turn on without having to do a special deploy. Maybe you want the feature to only be live for a small period of time, and again instead of deploying to remove the feature you turn the flag off and nobody sees it any longer.

By the end of this post, you’ll have a service, guard, and directive that do just that for you. With those three items and a configuration file, you’re good to go.

The Configuration File

The last thing to cover is where to put your feature flags. The best place to put them to prevent you from needing to do a deploy to change the flag is in a JSON file that is loaded when the app bootstraps, or a database that is queried as the app bootstraps. If you use the environment.ts files built into Angular, it will work but you have to deploy each time you make a change because that is build time configuration, and not runtime configuration. You can read all about how to do that in this blog post of mine, Loading App Configuration in the APP_INITIALIZER. I’m not going to go over the whole process in this post of loading the configuration for the feature flags. I’ll only be covering how to make a service that uses that configuration for the feature flags.

I have an Angular package published on NPM that loads the configuration at runtime for you and provides it in a service. It’s called runtime-config-loader and makes the process of loading configuration straight forward. I will use that package in this demonstration. Please reach out to me if you have any questions, or check out the package on NPM where the README explains how to implement the package.

Feature Flags Service

Let’s start by building a service that is used to tell us if a given feature is turned on or not. The service will actually be pretty straightforward since we’ll be using the runtime configuration loader. We’ll get the feature flags into the service, and provide a method on the service to return true or false based on that flag’s value. Here is the service:

// feature-flags.service.ts

export class FeatureFlagsService {
	private featureFlags: any;

	constructor(private _config: RuntimeConfigLoader) {
		this.featureFlags = this._config.getConfigObjectKey('featureFlags');
	}

	isFeatureFlagEnabled(flag: string) {
		return this.featureFlags && this.featureFlags[flag];
	}
}

Not too complicated, huh? This allows us to use the service anywhere in our application. All we need to do is call the isFeatureFlagEnabled method and pass in the name of a flag. If there were no feature flags in the configuration, the flag doesn’t exist, or the flag is set to false, the method returns false. If the flag is turned on, the method returns true. Let’s look at an example of where this could be used in a component’s class:

// app.component.ts
export class AppComponent implements OnInit {
	constructor(private _featureFlags: FeatureFlagsService, private _analytics: AnalyticsService) {}

	ngOnInit() {
		if (this._featureFlags.isFeatureFlagEnabled('analytics')) {
			this._analytics.initialize();
		}
	}
}

In this example, we use the FeatureFlagsService to check if the analytics flag is turned on. If so, we call the initialize method on the AnalyticsService. If not we’d just skip this part of the ngOnInit method. There could be a lot of reasons why we don’t want analytics turned on. For example, maybe we only want them turned on in production. In this case, the if statement will be in the code forever. It could also be because the feature is still being fine tuned, so we’ll want the feature turned on in local development and in a test environment, but not production. When the feature is ready, you can remove the if statement.

That’s all we have to do to create and use the service! Let’s now move on to creating a directive.

Feature Flag Directive

Directives in Angular come in three flavors: components, structural and attribute directives. We’re going to create and use a structural directive, like *ngIf. When we use this directive, we’ll pass in the feature flag that should determine if a particular portion of the app shows up in the template or not. If the feature flag is turned on, the content will be left in the template. If the flag is not available or turned off, the content will be removed from the template.

To be completely honest, you don’t technically need this directive. You could use the service from the previous section to query for the flag, and then use the *ngIf directive that’s built into Angular. But with the feature flags directive, you don’t need to import the service into your component. All you need to do is use the directive. With all that being said, you can do whatever works best for you.

Let’s get to building the directive. Here’s the final version of the code:

@Directive({
	selector: '[featureFlag]',
})
export class FeatureFlagDirective implements OnInit {
	private requiredFlag: string = '';
	private isHidden = true;

	@Input() set featureFlag(val) {
		if (val) {
			this.requiredFlag = val;
			this.updateView();
		}
	}

	constructor(
		private _templateRef: TemplateRef<any>,
		private _viewContainer: ViewContainerRef,
		private _featureFlags: FeatureFlagsService,
	) {}

	ngOnInit() {
		this.updateView();
	}

	private updateView() {
		if (this.checkValidity()) {
			if (this.isHidden) {
				console.log('going to create the view');
				this._viewContainer.createEmbeddedView(this._templateRef);
				this.isHidden = false;
			}
		} else {
			this._viewContainer.clear();
			this.isHidden = true;
		}
	}

	private checkValidity() {
		return this.requiredFlag && this._featureFlags.isFeatureFlagEnabled(this.requiredFlag);
	}
}

I am not going to go over all the details of creating a structural directive in this article. If you’d like to learn more, you can read this article by Dmitry Nehaychik or this one by Netanel Basal. We’ll just cover the method of using the directive, which is determined in the decorator, and point out that the FeatureFlagsService is used in the checkValidity method to see if the flag is turned on or not. Let’s look first at the decorator:

@Directive({
  selector: "[featureFlag]",
})

The selector here means that to use the directive, we’ll need to add the selector to the HTML tag like we would any other HTML attribute. This is an example:

<div *featureFlag="'thisFlagExists">
	<p>Because the flag exists, this content will stay on the page.</p>
</div>
<div *featureFlag="'thisFlagDoesntExist">
	<p>Because the flag doesn't exist, this content will be removed from the page.</p>
</div>

Now let’s look at the checkValidity method. The method does two things. First, it checks that the flag passed in as an @Input exists. Second, it checks the FeatureFlagsService to see if the flag is enabled. The return value is true if both those conditions are met. Otherwise it’s false. If the return value is true, the content is left on the screen (or added if it was previously removed). If the return value is false, the content is removed from the screen.

private checkValidity() {
  return this.requiredFlag && this._featureFlags.isFeatureFlagEnabled(this.requiredFlag);
}

We now have a service that can be used in component class files to check for flags as well as a directive to show and hide content based on the flag.

Route Guard

The last thing to cover in this blog post is a route guard that uses the feature flag service. This will prevent users from getting to portions of the application that are not ready. This guard will be similar to any other guard, like an authentication guard. Essentially, a feature flag to check for validity will be provided in the route data. If the flag is enabled, the user will be directed to the route. If not, they will be redirected to some other route. A desired redirect can be provided on the route data as well. I covered this in more depth in this blog post.

First, here’s the code for the guard:

export class FeatureFlagGuard implements CanActivate {
	constructor(private _featureFlags: FeatureFlagsService, private _router: Router) {}
	canActivate(next: ActivatedRouteSnapshot): boolean | UrlTree {
		const requiredFeatureFlag: string = next.data['requiredFeatureFlag'] as string;
		const featureFlagRedirect: string = (next.data['featureFlagRedirect'] as string) || '/';

		return this._featureFlags.isFeatureFlagEnabled(requiredFeatureFlag)
			? true
			: this._router.createUrlTree([featureFlagRedirect]);
	}
}

In the canActivate method, the required feature flag and redirect were taken from the route’s data. The feature flag service is used to check if the flag is turned on or not. If it is, they are allowed to go to the route (or true is returned). Otherwise, the redirect provided is used by returning a new UrlTree. Here’s an example of a route definition where the flag and redirect are provided:

@NgModule({
	imports: [
		RouterModule.forRoot([
			{
				path: 'experimental-route',
				loadChildren: () =>
					import('/path/to/module').then(
						(module) => module.ExperimentalModuleName,
					),
				canActivate: [FeatureFlagGuard],
				data: {
					requiredFeatureFlag: 'myFeatureFlag',
					featureFlagRedirect: '/path/for/redirect'
				},
			}
		])
	]
})

In this route definition, we provide the FeatureFlagGuard in the canActivate array. There’s also a data object. This data can be used in guards (or in the components) later on. In this case, a requiredFeatureFlag and featureFlagRedirect are provided for the guard to use.

With this guard, new routes will be available as soon as the flag is toggled on. When the flag is off, the user can not navigate to those sections of the app. This allows for deploys to continue while features are being worked on. Then once they’re ready the flag is turned on and the routes can be accessed, even without another deploy. Also, in the worst-case scenario, you can turn the flag back off if something doesn’t go right.

Conclusion

Feature flags are an important part of a continuous development workflow, where deploys are made even before features are finished. Combining feature flags with runtime configuration allows changes to be made at any time without a new deploy being done. Using a combination of guards, the service, and the directive, all parts of your application should have access to the feature flags.

Click here to subscribe to the newsletter and be the notified when a new blog post is available!