Angular - Custom Structural Directives
Angular concept AKA ngIf
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 theifHasPermissionon will pass some data to ourIfHasPermissionDirectiveset: 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.
Now in ifHasPermission() we use a ternary to say:
if(I have the permission){
"Display the element"
}else{
"Don't display the element"
}
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.