Feature flags in Angular
Tags: angular, javascript, featureflag, directives
Published: 2023-10-02 19:00:39

Recently, I have found myself more convinced about using feature flags in my Angular projects. I was skeptical at first because the code can become quite a mess and hard to maintain properly. But, I have come to appreciate the pros.

I have collaborated with clients who followed a bi-weekly release schedule that came with some challenges. Tying feature releases to these fixed schedules made it really restrictive. We couldn't always adhere to, for example, the legal department. All of our feature releases happened on the scheduled release. However, with the adoption of feature flags, the flexibility to release on any day became feasible. Moreover, the feature flags enable smoother rollbacks in the face of unforeseen issues.

Another advantage is that feature flags enable the developer to create small pull requests. They can merge slices of a feature without immediately deploying.

## What is a feature flag?
A feature flag, also known as a feature toggle or feature switch, is a programming technique that allows developers to turn specific features or functionalities of a software application on or off, usually during runtime. 

As an illustration, in this article, we will be discussing the `fastLogin` feature flag. The feature enables a streamlined login process, making it into a single step. Without the feature flag, you would have to contact customer support, which gives you a code to log in (bad UX, but you get the point).


![On the left the application with the fastLogin feature disabled. It shows a wireframe of an application. The wireframe has the text: "Contact customer support to login" with a contact button. On the right a wireframe with fastLogin enabled. This shows two inputs username and password with a button to log in.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lndr57rfk1chuaaldbpr.png)

## Getting started
In this example, we will be using an API to retrieve the feature flags that are active, but you could also use a `json` file in the code, up to you. First, let's define the structure of the API response.

```typescript
// feature-flag.service.ts

type FeatureFlagResponse = {
  fastLogin: boolean;
  fastRegister: boolean;
  fastSettings: boolean;
}
```
The `FeatureFlagResponse` consists of features returned by the API. In this case `fastLogin`, `fastRegister` and `fastSettings`.

Managing active feature flags can become quite cumbersome. It is crucial to sync the types with the backend. So it is clear which feature flags are active.

By using the keys within the `FeatureFlagResponse`, we can scope our functions to only have access to the keys in the `FeatureFlagResponse`. This narrows down the potential feature flags you could use in the front end. If it doesn't exist in the response, you can't use it.

```typescript
// feature-flag.service.ts

type _FeatureFlagKeys = keyof FeatureFlagResponse;
```
It is worth noting that I have opted against using only `keyof FeatureFlagResponse`. By transforming the types, the output becomes `"fastLogin" | "fastRegister" | "fastSettings"`, providing a more user-friendly experience when inspecting types.

```typescript
// feature-flag.service.ts

export type FeatureFlagKeys = {
  [K in _FeatureFlagKeys]: K;
}[_FeatureFlagKeys]
```

![Shows the infobox in Visual Studio Code when you hover over an variable. In this case the _FeatureFlagsKeys. The infobox says the type is keyof FeatureFlagsResponse.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/131rcjxmfwjn12jyr83q.png)


![Shows the infobox in Visual Studio Code when you hover over an variable. In this case the FeatureFlagsKeys. The infobox says the type is fastLogin, fastRegister or fastSettings.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1jlwa5vjmep9dzoj0zpc.png)

Now, let's integrate the method to retrieve feature flags and store them into a signal. The signal is being used to verify whether a specific feature is currently enabled or not.

```typescript
// feature-flag.service.ts

@Injectable({ providedIn: 'root' })
export class FeatureFlagService {
  http = inject(HttpClient);
  features = signal<Record<string, boolean>>({});

  getFeatureFlags(): Observable<FeatureFlagResponse> {
    return this.http.get<FeatureFlagResponse>('/api/flags').pipe(tap((features) => this.features.set(features)));
  }

  getFeature(feature: FeatureFlagKeys): boolean {
    return this.features()[feature] ?? false;
  }
}
```

We can leverage the service to invoke the API, but what is the optimal place for this action? A suitable moment is during app initialization, and fortunately, Angular offers a DI token to provide one or more initialization functions.

By creating a custom provider function we can adhere to the new Angular standard by calling `provideFeatureFlag()`. The factory is being used to initialize the feature flags.

```typescript
// feature-flag.provider.ts

function initializeFeatureFlag(): () => Observable<any> {
  const featureFlagService = inject(FeatureFlagService);
  return () => featureFlagService.getFeatureFlags();
}

export const provideFeatureFlag = () => ({
  provide: APP_INITIALIZER,
  useFactory: initializeFeatureFlag,
  deps: [],
  multi: true,
})
```

```typescript
// app.config.ts

export const appConfig: ApplicationConfig = {
  providers: [
    ...
    provideHttpClient(),
    provideFeatureFlag(),
  ],
};
```
The feature flags are now accessible from everywhere in the app. The next consideration is integrating them into component templates. One approach could be injecting the `FeatureFlagService` wherever it is required. Alternatively, we could use a structural directive.

```typescript
// feature-flag.directive.ts

@Directive({ selector: '[featureFlag]', standalone: true })
export class FeatureFlagDirective {
  templateRef = inject(TemplateRef);
  viewContainer = inject(ViewContainerRef);
  featureFlagService = inject(FeatureFlagService);
  hasView = signal(false);

  @Input() set featureFlag(feature: FeatureFlagKeys) {
    if (this.featureFlagService.getFeature(feature) && !this.hasView()) {
      this.viewContainer.createEmbeddedView(this.templateRef);
      this.hasView.set(true);
    } else {
      this.viewContainer.clear();
      this.hasView.set(false);
    }
  }

  @Input() set featureFlagElse(elseTemplateRef: TemplateRef<any>) {
    if (!this.hasView()) {
      this.viewContainer.createEmbeddedView(elseTemplateRef);
    }
  }
}
```
This directive makes it possible to display content only when a specified feature is enabled. Additionally, it offers the flexibility to include an alternative template for when the feature is not enabled. For example when `fastLogin` is enabled we can log in with one click. If it's not enabled, we will have to mail the customer support.

```html
<div *featureFlag="'fastLogin'; else notEnabled">
  <button>Login with one click</button>
</div>
<ng-template #notEnabled>
  <div>Mail us one support@example.com for your login.</div>
</ng-template>
```
It is worth mentioning that the `FeatureFlagKeys` from earlier ensure that the autocomplete feature exclusively suggests the options typed in the `FeatureFlagResponse` such as `fastLogin`, `fastRegister` and `fastSettings`.


![The autocomplete feature in Visual Studio Code shows immediately fastLogin, fastRegister and fastSettings.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g8vq5pucnfi1g4vjdzho.png)

Attempting a random string will result in a build error, ensuring we stick to the set feature flags.


![Build error in Visual Studio Code because it is using a non existing feature flag as input for the directive.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bkt7z1mxcz5hhr8c19a2.png)

This strict typing not only makes the development process smoother but also acts as a strong mechanism for quickly spotting references to feature flags that have been removed, making the cleanup process much easier.

But, what about routes? We could create a route guard that utilizes the service as well.

```typescript
// feature-flag.guard.ts
export const featureFlagGuard = (feature: FeatureFlagKeys) => {
  return () => {
    const featureFlagService = inject(FeatureFlagService);
    return featureFlagService.getFeature(feature);
  };
};

// routes.ts
{
  path: 'fast-register',
  canMatch: [featureFlagGuard('fastRegister')],
  loadComponent: () => ..,
}
```

Or you could just use it inline.

```typescript
canMatch: [() => inject(FeatureFlagService).getFeature('fastLogin')]
```
And there we go. Feature flags in Angular. If you have questions or use cases that we could tackle. Please let me know.