A better feature flag directive in Angular
Tags: angular, typescript, structuraldirective, featureflags
Published: 2022-10-11 12:56:33

---
tags: angular, typescript, structuraldirective, featureflags
---  

Custom structural directives are powerful. They promote a declarative approach and help you keep your Angular components DRY and clean. In this article, we are going to build a simple yet useful structural directive that will show one or another piece of the template depending on the value of the feature flag. Here is what we expect from our feature flag directive's public interface:

- Directive will allow conditionally showing parts of the template depending on the feature flag's status.
- No additional code should be needed in the component class apart from importing the directive itself to make it available in the template.

### How is this one better

Directives like this are quite common in Angular projects. But with a bit of additional knowledge, we can build a more powerful directive by implementing an option to use an alternative template to show in case the feature flag's value is falsy. This option is very convenient, and yet it is not used as often as it deserves, at least based on my experience working with several Angular codebases.
 
If you have used `*ngIf=A; else B` syntax in your Angular templates, you already know that it is possible to have a behavior like this. Here is how our structural directive will be used in the component's template:

```html
  <div *appIfFeatureFlag="'FEATURE_1'; else: defaultTemplate">Feature 1 template</div>
  <ng-template #defaultTemplate>Default template</ng-template>
```

We will also use some APIs that Angular team introduced not so long time ago. In particular, standalone directives and the `inject` function as an alternative to injecting via the constructor's arguments. 

### Why feature flag use case
We could pick any shared feature for the structural directive demo purpose. I'd hence like to say a couple of words about the reasoning behind picking the feature flags use case before we start.

The path to building bug-proof applications is long and challenging. It entails many things to learn and many practices to adopt. You need very good test coverage, correct abstractions, short development cycle to name a few. Introducing feature flags takes nowhere near as much time as mastering the above-mentioned practices. By no means do feature flags replace these things, but they are the right first step on this path. You get the option to roll back problematic changes in any environment within seconds without redeploying. 
 
I strongly believe that feature flags are a must-have thing in any application that runs in production. 

> This is crucial both for users who are no longer forced to use the broken feature waiting for the fix, and for developers who are thus able to address issues in non-stressful conditions.

### Implementation

Without further ado, let's create the directive. We will start with the directive's `Input`s and dependencies: 

```typescript
@Directive({
  selector: '[appIfFeatureFlag]',
  standalone: true
})
export class FeatureFlagDirective implements OnInit {
  @Input()
  appIfFeatureFlag!: string;
  @Input()
  appIfFeatureFlagElse?: TemplateRef<unknown>;

  private templateRef = inject(TemplateRef<unknown>);
  private viewContainerRef = inject(ViewContainerRef);
  private featureFlagService = inject(FEATURE_FLAGS_SERVICE);
 
  //...
}
```

We are using `standalone: true` to make our directive importable directly without `NgModule`. And define two inputs for the name of the feature flag we will be checking and the alternative template to show in case of falsy flag value. The code is written in Typescript `strict` mode, so we explicitly mark optional and mandatory inputs with corresponding assertion operators. 

Pay attention to how these inputs are named. 

> To use the desired syntax in templates we need to prefix all additional inputs with the name of the directive, which is `appIfFeatureFlag` in our case. 

Hence, if we want to specify the alternative `ng-template` to show in the `else` parameter, we should name the relevant input `appIfFeatureFlagElse`. 

Bear in mind how the last part of the input's name written in camel case turns into lower case automatically when the directive is used in the template:
 
```html
<div *appIfFeatureFlag="'FEATURE_1'; else: defaultTemplate">Feature</div>
```

And then we inject the following providers: 

- `templateRef` - holds the reference to the template we want to show if the feature flag in question is turned on, i.e. the content of the element `*appIfFeatureFlag` is attached to. 

If we removed the `*` symbol at the beginning of the directive's name we would get a `No provider for TemplateRef found` error. As you see, we don't specify anywhere in the `@Directive` decorator's metadata that this is a [structural](https://angular.io/guide/structural-directives) directive as opposed to [attribute](https://angular.io/guide/attribute-directives) directive. Instead, the `*` symbol serves as syntactic sugar for us, wrapping the host element in the following construction:

```html
<ng-template [appIfFeatureFlag]="'FEATURE_1'">
  <div>Feature</div>
</ng-template>
```

The dependencies directive injects are:

- `viewContainerRef` - the [container](https://angular.io/api/core/ViewContainerRef) used to create embedded views withing the current host element

- `featureFlagService` - the service exposing APIs to work with feature flags provider. We will shed some light on good practices when working with similar dependencies further in the article.

Since Angular 14 we get these providers without injecting them via the `constructor` with help of the `inject` function that now can be called during the component's construction phase. 

Now let's implement the view creation logic:

```typescript
  async ngOnInit() {
    try {
      const featureFlag = await this.featureFlagService.getFeatureFlag(this.appIfFeatureFlag);
      featureFlag ? this.onIf() : this.onElse();
    } catch (error) {
      this.onElse();
      // additional error handling logic goes here
    }
  }
  
  private onIf(): void {
    this.createView(this.templateRef);
  }
  
  private onElse(): void {
    if (!this.appIfFeatureFlagElse) {
      return;
    }
    
    this.createView(this.appIfFeatureFlagElse);
  }
  
  private createView(templateRef: TemplateRef<unknown>): void {
    this.viewContainerRef.createEmbeddedView(templateRef);
  }
```

What happens here is pretty straightforward. When the component is initialized, the directive fetches the feature flag's value and depending on the result calls either `onIf` method that creates the view of the provided `templateRef` or `onElse` method that checks if the fallback template was provided to create an alternative view. 

The `createView` method just calls the relevant method of the `viewContainerRef` to create a view from the provided `templateRef`.

### Preparing dependencies

For our feature flag directive to work we need a way to get the value of the feature flag. Let's create an interface `FeatureFlagsService` and an injection token `FEATURE_FLAGS_SERVICE` with this interface passed as a generic. The interface will contain a single method `getFeatureFlag` returning a `Promise` that should resolve to a boolean value. 

```typescript
export interface FeatureFlagsService {
  getFeatureFlag(flagName: string): Promise<boolean>;
}

export const FEATURE_FLAGS_SERVICE = new InjectionToken<FeatureFlagsService>('feature.flags.service');
```

Why not just create a `FeatureFlagService`? 

> We want our directive to depend on abstraction instead of implementation. 

This allows us to easily use different `FEATURE_FLAGS_SERVICE` implementations behind this token depending on the context. For example, we might decide to switch to a different feature flags provider in the future or use a local stub service when using this directive in tests.  

### Directive in action
Let's test our directive in battle. For this demo purpose, we will use a stub for the `FeatureFlagsService` service implementation that will return `true` for _FEATURE_1_ and `false` for _FEATURE_2_:

```typescript
const FEATURE_FLAGS_MOCK: Record<string, boolean> = {
  FEATURE_1: true,
  FEATURE_2: false
}

@Injectable({providedIn: 'root'})
export class FeatureFlagServiceMock implements FeatureFlagsService {
  public getFeatureFlag(featureFlag: string): Promise<boolean> {
    return Promise.resolve(FEATURE_FLAGS_MOCK[featureFlag]);
  }
}
```

Now let's import the directive. Since it's a `standalone` directive, we can import it both in Angular modules and in standalone entities. 

```typescript
  imports: [FeatureFlagDirective]
``` 

And use one in the template:

```html
  <div *appIfFeatureFlag="'FEATURE_1'; else: defaultTemplate">Feature 1 template</div>
  <div *appIfFeatureFlag="'FEATURE_2'; else: defaultTemplate">Feature 2 template</div>
  <ng-template #defaultTemplate>Default template</ng-template>
```

This gives us the following result:

```
Feature 1 template
Default template
```

As you see, our conditional display logic becomes declarative and very concise.   

### Possible enhancements

The directive we built is deliberately simple. But depending on your application's needs you might want to support additional features like:

- Flag values different from `true`/`false`, e.g. strings.
- Sub-properties of feature flags (sometimes called feature variables in third-party services).
- Reacting to `appIfFeatureFlag` changes instead of running the check once in `ngOnInit` lifecycle hook. It will make your directive more universal, allowing you to pass the feature flag's name dynamically if there is a use case for this.  

Keep in mind that reasonable default values are your friends when adding additional options.  

### Links

You can find the complete version of the code by [this link](https://github.com/aGoncharuks/ng-workspace/tree/main/projects/ng-techniques/src/app/directives/structural-with-params).

Read more about structural directives in the [official docs](https://angular.io/guide/structural-directives).

### Wrapping up

I hope that the practices described in this read and the results we achieved will motivate you to delegate more work to structural directives. If you haven't been doing that already in your Angular projects:) 

Thanks for reading and see you in future articles!