Wednesday, July 7, 2021

How to Create a Custom Structural Directive in Angular

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

  1. How to Create a Custom Attribute Directive in Angular
  2. Directives in Angular
  3. How to Use ngFor and ngIf on Same Element in Angular
  4. Angular ngSwitch Directive With Examples
  5. Angular ngFor Directive With Examples

You may also like-

  1. Nested Route (Child Route) in Angular
  2. Angular CanActivateChild Guard to protect Child Routes
  3. FormBuilder in Angular Example
  4. Angular HttpClient - Set Response Type as Text
  5. Java ReentrantReadWriteLock With Examples
  6. Private Methods in Java Interface
  7. Try-With-Resources in Java With Examples
  8. How to Inject Prototype Scoped Bean into a Singleton Bean in Spring

No comments:

Post a Comment