Angular - Custom Structural Directives

GuileasGuileas
3 min read

I've sometimes asked myself what are *ngIf and *ngFor in Angular but never dig into it before until I faced a case where none of the knowledge I had about Angular concept at that time (services, validators, guards, interceptors, directives) would give me the answer I needed.

What I wanted to do, was an *ngIf like element that I could call in my component to manage my user permissions, something that I would call: *ifHasPermission

It's by searching for more details about *ngIf that I discovered: Structural Directives

What is this?

Structural Directives are the handyman that change the structure of the DOM by adding or removing elements from it.

They are prefixed with * and if you're using Angular I'm sure you know some of them: *ngIf, *ngFor are the most well known.

How to make one?

Let's create our *ifHasPermission structural directive.

I want to be able to use this structural directive like so:

<button *ifHasPermission="user.features.includes('PROFILE')"></button>

For each element of my view to be displayed only if the user feature array contains the required element.

Let's begin with the empty shell of a classic directive :

import { Directive, Input } from "@angular/core";

@Directive({
  selector: "[ifHasPermission]",
})
export class IfHasPermissionDirective {

  constructor() {}

  @Input() set ifHasPermission(condition: boolean) {}
}

Here nothing complexe a simple @Directive decorator with a selector param defining the name of your directive (don't forget the [ ] ).

Then a class IfHasPermissionDirective contains an empty constructor and a ifHasPermission function.

If you're not familiar with the @Input set syntax you can undestand it like so:

  • @Input : Parent to child data transmission, in our case it means that the component we will use the ifHasPermission on will pass some data to our IfHasPermissionDirective

  • set : on change, the property will be equal to the new value

Now let's add the magic :

import { Directive, Input, TemplateRef, ViewContainerRef } from "@angular/core";

@Directive({
  selector: "[ifHasPermission]",
})
export class IfHasPermissionDirective {

  constructor(
    private readonly templateRef: TemplateRef<any>,
    private readonly viewContainer: ViewContainerRef
  ) {}

  @Input() set ifHasPermission(condition: boolean) {
    condition
      ? this.viewContainer.createEmbeddedView(this.templateRef)
      : this.viewContainer.clear();
  }
}

Let's break down this code:

In the constructor we inject two private elements in readonly.

  • TemplateRef: represents an embedded template that can be used to instantiate embedded views.

  • ViewContainerRef: represents a container where one or more views can be attached to a component.

💡
I will talk more about those two classes in another article that I will like to this one, I still have some work to do before explaining them to you in simple words.

Now in ifHasPermission() we use a ternary to say:

if(I have the permission){
    "Display the element"
}else{
    "Don't display the element"
}
💡
Don't forget to add your directive in your module to be able to use it

And TADAM! You now have a structural directive that you can use in all your application to conditionally display or hide the element of your page.

0
Subscribe to my newsletter

Read articles from Guileas directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Guileas
Guileas