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 theifHasPermission
on will pass some data to ourIfHasPermissionDirective
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.
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.