In this article we’ll see how to create a custom structural directive in Angular. Structural directive is used to modify DOM structure by adding or removing DOM elements.
In order to create a structural directive in Angular you should be clear about how the structural directives internally work so here is a small section on that.
The asterisk, *, syntax on a structural directive, such as *ngIf, is shorthand that Angular interprets into a longer form. Angular transforms the asterisk in front of a structural directive into an <ng-template> that surrounds the host element. For example
<div *ngIf="user" class="name">{{user.name}}</div>
is translated into a <ng-template> element, wrapped around the host element, like this.
<ng-template [ngIf]="user"> <div class="name">{{user.name}}</div> </ng-template>
Same way for *ngFor
<div *ngFor="let item of items"> {{item .name}} </div>
Above statement is internally expanded into a long form that uses the ngForOf selector on an <ng-template> element. Repeat the <ng-template> for each item in the list
<ng-template ngFor let-item [ngForOf]="items"> <div>{{item.name}}</div> </ng-template>
TemplateRef and ViewContainerRef classes
Angular provides a class named TemplateRef which represents an embedded template. TemplateRef helps you get to the <ng-template> contents.
There is also a class named ViewContainerRef which represents a container where one or more views can be attached to a component. It can contain host views (created by instantiating a component with the createComponent() method), and embedded views (created by instantiating a TemplateRef with the createEmbeddedView() method).
These two classes TemplateRef and ViewContainerRef are used to create new views and embed those views to the container respectively. That’s how the DOM structure is modified using the structural directive.
Custom directive Angular example
In this example we’ll create a structural directive which works as a for loop (similar to *ngFor).
1. Create a Directive class decorated with @Directive decorator.
You can use the following command to create a Directive class.
ng generate directive DIRECTIVE_NAME
It will generate DIRECTIVE_NAME.directive.ts Typescript file, a test file DIRECTIVE_NAME.directive.spec.ts and also updates app.module.ts to register the created directive.
Angular Custom structural directive code
Here is the full code for the custom structural directive that acts quite similar to ngForOf directive.
import { Directive, Input, OnChanges, SimpleChanges, TemplateRef, ViewContainerRef } from "@angular/core"; @Directive({ selector: '[appNgForLoop]' }) export class NgForLoopDirective implements OnChanges { @Input() appNgForLoopOf: Array<any>; constructor(private container: ViewContainerRef, private template: TemplateRef<any>) { } ngOnChanges(changes: SimpleChanges) { //console.log('in ngOnChanges'); this.container.clear(); for (const e of this.appNgForLoopOf) { this.container.createEmbeddedView(this.template, { $implicit: e, index: this.appNgForLoopOf.indexOf(e), }); } } }
Some important points to note here are-
1. Notice that the selector for the directive is enclosed in square brackets [''] which means that the selector name passed will be a property of an element, not used as an element itself.
2. Instances of TemplateRef and ViewContainerRef are injected. We have already discussed above why these two classes are needed.
3. There is a property having the same name as selector (with an additional ‘Of’).
@Input() appNgForLoopOf:Array<any>;
As already discussed when you use a structural directive it is internally expanded, for example *ngFor is expanded to this form
<ng-template ngFor let-item [ngForOf]="items"> <div>{{item.name}}</div> </ng-template>
Notice the part [ngForOf]="items", in the ngFor an ‘Of’ is added making it ngForOf which is bound to the items array. Same way our custom directive is also expanded to appNgForLoopOf.
Using the property with the same name decorated with @Input() decorator ensures that the appNgForLoopOf property holds the reference to the iterated array.
3. In our custom directive we have implemented the OnChanges lifecycle hook which is called when any data-bound property of a directive changes. Define an ngOnChanges() method to handle the changes, there loop through the items in the array and create an embedded view for each of them:
for (const e of this.appNgForLoopOf) { this.container.createEmbeddedView(this.template, { $implicit: e, index: this.appNgForLoopOf.indexOf(e), }); }
4. When calling createEmbeddedView() method of the ViewContainerRef you can also pass context as a second argument that will be used inside ng-template. Using the key $implicit in the context object will set its value as default.
With the custom directive ready you can use it to iterate an array. For example if I have an array of Students objects then usage is as given below-
<tr *appNgForLoop="let student of students;"> <td>{{student.name}}</td> <td>{{student.rollNo}}</td> </tr>
5. You can also pass index as third argument of the createEmbeddedView() method. It is the 0-based index at which to insert the new view into this container. You can also use this index variable to get the current index in your template.
<tr *appNgForLoop="let student of students; let i = index"> <td>{{i+1}}</td> <td>{{student.name}}</td> <td>{{student.rollNo}}</td> </tr>
That's all for this topic How to Create a Custom Structural Directive in Angular. If you have any doubt or any suggestions to make please drop a comment. Thanks!
>>>Return to Angular Tutorial Page
Related Topics
You may also like-
No comments:
Post a Comment