DEV Community

Mathias Remshardt
Mathias Remshardt

Posted on • Updated on

Let's build - Feature flags in Angular

The problem

In one of the projects I'm working on we wanted to show/hide certain elements/features at runtime.
The requirements were pretty simple:

  1. Manually enable and disable the toggle in a configuration file
  2. It should be possible to change the value at runtime


A well known solution for this are Feature Flags or Feature Toggles. A lot has already been written about this topic e.g. you can find some details here.
There are quite some ready made solutions out there. However most of the available solutions are SaaS offerings and these are not always an option (for technical or financial reasons).
So, as we don't have many (complicated) requirements, it shouldn't be too hard to build our own, local version.


The following diagram gives an overview of the main elements
As mentioned previously, we are aiming for a rather simple solution so there is not too much going on.



All of the configuration is stored in the feature.json file. It has a pretty simple structure with the key being the name of the feature and the value a boolean which indicates if the feature is enabled or not.
This is enough for basic use cases. However if there are a lot flags it could make sense to add bit more structure here e.g. nesting the different flags under a key which represents the pages or components they are associated with.

It is also possible to define different feature.json files as a sort of template for a specific configuration e.g. if the application is deployed for multiple customers.

  "newFeature": true

Enter fullscreen mode Exit fullscreen mode



The root injected service loads the feature.json file and stores it in memory.
The file could be validated and typed after loading (e.g. using something like zod).
I personally did not see any advantage in doing that as the file is only used in the isFeatureEnabled method which checks if the provided key is truthy. Having it typed would not give any benefit and has the drawback that the validation and type definition has to be updated whenever a new property is added to the feature.json file.

  init(): Observable<void> {
    const result = new ReplaySubject<void>(1);
      .get<typeof this.featuresFlags>('assets/feature.json')
        catchError(() => of({}))
      .subscribe((featureFlags) => {
        this.featuresFlags = featureFlags;;
    return result;
Enter fullscreen mode Exit fullscreen mode

For the sake of the example the loading mechanismn is kept pretty simple (e.g. it does not include any error handling, it hard codes the path...).

The init method is called by an APP_INITIALIZER at application start up.
It returns an Observable to make sure the first render does not happen before the feature.json file has been loaded.

const initFeatureServiceFactory = (featureService: FeatureService) => {
  return () => featureService.init();

  declarations: [AppComponent],
  imports: [
  providers: [
      provide: APP_INITIALIZER,
      useFactory: initFeatureServiceFactory,
      deps: [FeatureService],
      multi: true,
  bootstrap: [AppComponent],
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode


The aformentioned isFeatureEnabled method returns if the feature is enabled or not.
The implementation is pretty straight forward at the moment:

  isFeatureEnabled(name: string): boolean {
    return this.featuresFlags[name];
Enter fullscreen mode Exit fullscreen mode

Having a dedicated function hides the storage mechanism and allows for changing to a different approach in the future with minor impact on the using components.


For demonstration purposes two components have been added to the project


As the name implies the component does the feature check for itself by directly calling the isFeatureEnabled with the feature name.

export class WithoutDirectiveComponent {
  isNewFeatureEnabled = false;

  constructor(public featureService: FeatureService) {
    this.isNewFeatureEnabled = this.featureService.isFeatureEnabled('newFeature');

Enter fullscreen mode Exit fullscreen mode
<p *ngIf="isNewFeatureEnabled; else featureDisabled">
  WithoutDirectiveComponent: Feauture enabled
<ng-template #featureDisabled>
  <p>WithoutDirectiveComponent: Feature disabled</p>
Enter fullscreen mode Exit fullscreen mode


Repeating the "check and render" is a bit tedious and identical for every using component. Therefore a directive has been added to the project that takes care of this part. It's working similar to ngIf but takes the feature name instead of a boolean.

The directive passes the provided feature name to the FeatureService and leverages the ngIf directive for hiding or showing the feature flagged element.
Similar to ngIf it also allows for displaying an alternative template should the feature be disabled.

  selector: '[appIfFeatureEnabled]',
  standalone: true,
  hostDirectives: [
      directive: NgIf,
      inputs: ['ngIfElse: appIfFeatureEnabledElse'],
export class IfFeatureEnabledDirective {
  @Input() set appIfFeatureEnabled(appIfFeatureEnabled: string) {
    this.ngIf.ngIf = this.featureService.isFeatureEnabled(
      appIfFeatureEnabled ?? false

  constructor(private ngIf: NgIf, private featureService: FeatureService) {}
Enter fullscreen mode Exit fullscreen mode


The component just needs to pass to feature name to the FeatureDirective

<p *appIfFeatureEnabled="'newFeature'; else featureDisabled">
  WithDirectiveComponent: Feature enabled
<ng-template #featureDisabled
  >WithDirectiveComponent: Feature disabled</ng-template
Enter fullscreen mode Exit fullscreen mode


This is it, a simple solution for feature flagging templates elements in Angular.
As mentioned in the beginning, the implementation only covers simple hide/show use cases which can often be enough, especially in the beginning.
In our project, it has served us as well so far.
It was easy and fast to build, it's easy to maintain (e.g. adding new feature flags) and avoids adding another dependency to a third party library (or company) and, most importantly, it fulfils all of our current requirements.

The code can be found here

Top comments (0)