26
loading...
This website collects cookies to deliver better user experience
npx create-nx-workspace --preset=angular --prefix=csd --appName=custom-structural-directive
# or
# ng new custom-structural-directive --prefix=csd
NgIf
directive. We will call it CsdIf
(CSR prefix stands for Custom Structural Directive :))ng generate module if
ng generate directive if/if --module if
# or shorthand
# ng g m if
# ng g d if/if --module if
import { Directive } from '@angular/core';
@Directive({
selector: '[csdIf]',
})
export class IfDirective {
constructor() {}
}
true
.<h2 *csdIf="true">My visible conditional header</h2>
<h2 *csdIf="false">My hidden conditional header</h2>
@Input
)TemplateRef
)ViewContainerRef
)@Input
decorator. The important thing is to use a proper naming convention. For it to work as it does in the example code shown above, we need to name the property the same as the attribute's selector:import { Directive, Input } from '@angular/core';
@Directive({
selector: '[csdIf]',
})
export class IfDirective {
@Input() csdIf: boolean = false;
constructor() {}
}
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[csdIf]',
})
export class IfDirective {
@Input() csdIf: boolean = false;
constructor(
private templateRef: TemplateRef<unknown>,
private vcr: ViewContainerRef
) {}
}
ViewContainerRef
's createEmbeddedView
method to display and clear
method to remove the content.csdIf
property is assigned already, we need to use ngOnInit
lifecycle hook.import {
Directive,
Input,
OnInit,
TemplateRef,
ViewContainerRef,
} from '@angular/core';
@Directive({
selector: '[csdIf]',
})
export class IfDirective implements OnInit {
@Input() csdIf: boolean = false;
constructor(
private templateRef: TemplateRef<unknown>,
private vcr: ViewContainerRef
) {}
ngOnInit(): void {
if (this.csdIf) {
this.vcr.createEmbeddedView(this.templateRef);
} else {
this.vcr.clear();
}
}
}
<h2 *csdIf="true">My visible conditional header</h2>
<h2 *csdIf="false">My hidden conditional header</h2>
<input id="showInput" type="checkbox" [(ngModel)]="showInput" />
<label for="showInput">Show conditional header</label>
<h2 *csdIf="showInput">My conditional header</h2>
showInput
, our header doesn't disappear as we would expect. This is because we only check the csdIf
input value inside of ngOnInit
, but we do not react to the input's changes. To resolve this, we can either use ngOnChanges
lifecycle hook or modify the csdIf
to be a setter rather than just a property. I will show you the later solution but implementing it using ngOnChanges
should be very similar.csdIf
to be a setter, and store its value in a private property show
.@Directive({
selector: '[csdIf]',
})
export class IfDirective implements OnInit {
private show = false;
@Input() set csdIf(show: boolean) {
this.show = show;
}
/* constructor */
ngOnInit(): void {
if (this.show) {
this.vcr.createEmbeddedView(this.templateRef);
} else {
this.vcr.clear();
}
}
}
csdIf
value is set, we need to perform the same logic as we do in ngOnInit
. We need to make sure though that we don't render the template twice so we can clear the view first in all cases.@Directive({
selector: '[csdIf]',
})
export class IfDirective implements OnInit {
private show = false;
@Input() set csdIf(show: boolean) {
this.show = show;
this.vcr.clear();
if (this.show) {
this.vcr.createEmbeddedView(this.templateRef);
}
}
/* constructor */
ngOnInit(): void {
this.vcr.clear();
if (this.show) {
this.vcr.createEmbeddedView(this.templateRef);
}
}
}
@Directive({
selector: '[csdIf]',
})
export class IfDirective implements OnInit {
private show = false;
@Input() set csdIf(show: boolean) {
this.show = show;
this.displayTemplate();
}
/* constructor */
ngOnInit(): void {
this.displayTemplate();
}
private displayTemplate() {
this.vcr.clear();
if (this.show) {
this.vcr.createEmbeddedView(this.templateRef);
}
}
}
<input id="showInput" type="checkbox" [(ngModel)]="showInput" />
<label for="showInput">Show conditional header</label>
<h2 *csdIf="showInput">My conditional header</h2>
CsdIf
directive shows and hides the content based on the boolean input correctly. But the original NgIf
directive allows for specifying an alternative template via the "else" property as well. How do we achieve this behavior in our custom directive? This is where understanding the "syntactic sugar" that stands behind the structural directives is crucial. The following NgIf
syntax:<h2 *ngIf="show; else alternativeTemplate">My conditional header</h2>
<ng-template #alternativeTemplate>
<h2>My alternative header</h2>
</ng-template>
<ng-template [ngIf]="show" [ngIfElse]="alternativeTemplate">
<h2>My conditional header</h2>
</ng-template>
<ng-template #alternativeTemplate>
<h2>My alternative header</h2>
</ng-template>
else
property is actually becoming ngIfElse
input parameter. In general, we can construct the property name by concatenating the attribute following *
and the capitalized property name (eg. "ngIf"
+ "Else"
= "ngIfElse""
). In case of our custom directive it will become "csdIf"
+ "Else"
= "csdIfElse
<h2 *csdIf="show; else alternativeTemplate">My conditional header</h2>
<ng-template #alternativeTemplate>
<h2>My alternative header</h2>
</ng-template>
<ng-template [csdIf]="show" [csdIfElse]="alternativeTemplate">
<h2>My conditional header</h2>
</ng-template>
<ng-template #alternativeTemplate>
<h2>My alternative header</h2>
</ng-template>
csdIfElse
property. Let's add and handle that property in the custom directive implementation:@Directive({
selector: '[csdIf]',
})
export class IfDirective implements OnInit {
private show = false;
@Input() set csdIf(show: boolean) {
this.show = show;
this.displayTemplate();
}
@Input() csdIfElse?: TemplateRef<unknown>;
/* constructor */
ngOnInit(): void {
this.displayTemplate();
}
private displayTemplate() {
this.vcr.clear();
if (this.show) {
this.vcr.createEmbeddedView(this.templateRef);
} else if (this.csdIfElse) {
this.vcr.createEmbeddedView(this.csdIfElse);
}
}
}