Angular Access Right And Feature Toggle By Extending NgIf!
Tags: angular, programming
Published: 2023-04-13 13:39:48

[Jump to final solution](#extending-raw-ngif-endraw-)

During your Angular journey, you could be invested into a development that implies hiding elements to some kind of users depending **access right** or roles. You could even need to **hide complete features** from you Angular client waiting for the backend to be implemented or waiting for the feature to be officially released to the public.

I can already hear it from you, when it comes to display or hide components or other elements from a template, the first thing that comes in mind is to create a directive. It's reusable and **we love reusable things**.

Let's continue our story with our second example. We need to implement a directive that hide the element based on a given feature. Let us suppose that we have a feature called: _githubLogin_. Basically, this feature active the authentication via GitHub.

## Creating the directive (the naive way)

And Abracadabra, we have our nice `FeatureToggleDirective`.

```typescript
@Directive({ selector: '[feature]' })
export class FeatureToggleDirective implements OnInit {

    constructor(
        private viewContainer: ViewContainerRef,
        private featureService: FeatureService) {
    }

    @Input()
    feature: string;

    ngOnInit() {
        if (this.featureService.isDisabled(this.feature)) {
            this.hide();
        }
    }

    private hide(): void {
        this.viewContainer.clear();
    }
}
```

It's used like this :

```html
<button [feature]="'githubLogin'">Login via GitHub</button>
```

Meaning that the button *Login via GitHub* is displayed if the feature is enabled.

So here we are, we created our directive, and you noticed that I used `ViewContainerRef` to hide the content. It's a choice, I could also inject `ElementRef` and use
```typescript
this.elementRef.nativeElement.style.display = 'none';
```
to hide the content. Both solutions are valid, and we will go further in the comparison since it's not the goal of this article.

## Can we do better?

Of course, we can do better! What if we want to change the behavior of this directive to be more dynamic and flexible.

For example, being able to use it when the feature name is given asynchronously:
```html
<button [feature]="featureName$ | async">Login via GitHub</button>
```
We would need to use a setter or implement `OnChange.`

We could also decide to display a message explaining why this feature is disabled. By providing a fallback template to the directive:
```html
<button [feature]="'githubLogin'" [featureFallbackTemplate]="fallback">Login via GitHub</button>
<ng-template #fallback>Sorry, the GitHub feature is not yet available</ng-template>
```

There is so much work to do to make our directive completely robust. At some point, it begins to be as dynamic and robust as another directive that we all know...

## Extending `NgIf`

The subtitle says it all, why not reusing the code of `NgIf`. It has been proved that it works. It's the most used directive in the Angular world (_"ngIf"_ gives 4M file results in Github!). So what do we do ? Do we copy the NgIf solution? What about **extending NgIf**? Yes, sounds good!

Let's take a look at the [NgIf code](https://github.com/angular/angular/blob/main/packages/common/src/directives/ng_if.ts). We notice 4 methods: 3 setters and one private method where all the magic occurs.

Since our directive has its own selector name `feature`, we need to rename the setters with `feature`, `featureThen`, and `featureElse`. Each of those methods will act as a proxy and call the corresponding setter in `NgIf` class.

We also need to add the `FeatureService` logic in the feature setter. Our condition returns a `boolean`, so we set the type of the `NgIf` to `boolean`.

```typescript
@Directive({ selector: '[feature]' })
export class FeatureToggleDirective extends NgIf<boolean> {

  @Input()
  set feature(feature: string) {
    this.ngIf = this.featureService.isEnabled(feature);
  }

  @Input()
  set featureThen(templateRef: TemplateRef<NgIfContext<boolean>>|null) {
    this.ngIfThen = templateRef;
  }

  @Input()
  set featureElse(templateRef: TemplateRef<NgIfContext<boolean>>|null) {
    this.ngIfElse = templateRef
  }

  constructor(private featureService: FeatureService ,_viewContainer: ViewContainerRef, templateRef: TemplateRef<NgIfContext<boolean>>) {
    super(_viewContainer, templateRef);
  }
}
```

And that's it, you can now use all the power of `NgIf` with your feature toggle directive! Amazing, isn't it?

## Some usage examples

Some examples of how we can use our directive:

Simple form with shorthand syntax:
```html
<button *feature="'githubLogin'">GiHub login</button>
```

Simple form with expanded syntax:
```html
<ng-template [feature]="'githubLogin'">
    <button>GiHub login</button>
</ng-template>
```

Shorthand form with an `else` block:
```html
<button *feature="'githubLogin'; else fallback">GiHub login</button>

<ng-template #fallback>
    <i>Sorry, the feature is not yet available</i>
</ng-template>
```

Shorthand form with `then` and `else` blocks:
```html
<ng-container *feature="'githubLogin'; then github; else fallback">GiHub login</ng-container>

<ng-template #github>
    <button>GiHub login</button>
</ng-template>
<ng-template #fallback>
    <i>Sorry, the feature is not yet available</i>
</ng-template>
```

## Working example

Take a look at the working online example here: [StackBlitz](https://stackblitz.com/edit/angular-ivy-7fkk6h?file=src/app/app.component.html). Play with it and fork it!

## Conclusion

We created a highly reusable directive based on a simple feature toggle example. There are plenty of other features that could reuse `NgIf`. I hope this article will give you more ideas and uses cases to build even more directives of this kind.

**Happy coding!**