Introduction
In the previous article of the series Learning Angular 2 step by step, we have learned about Angular 2 Attribute Directive, Where we have seen how to use built-in attribute directive and how to create our own custom attribute directive.In this tutorial, we will learn about Angular 2 Structural directives. Here, first of all, we will see what is Structural directive and how to use it and then we will learn to create our own custom Structural directive.
Let's start with structural directives.
What are structural directives?
Structural directives are used for changes the DOM layout by adding and removing DOM elements. like ngFor. ngFor structural directive adds or removes elements to the DOM depending on how many elements are there in the given array.Let's see how to use Structural directives in an Angular 2 application.
How to use built-in Structural directives?
There are some most commonly used built-in Structural directives like ngFor, ngIf etc. Let's see how to use ngFor Structural directive.The ngFor directive is a repeater directive, it adds/removes elements in our DOM depending on how many elements are in the given array. So for using this ngFor directive, we must have an array list first.
In the "Angular 2 Component" tutorial, We had created an EmployeeList component for showing list of employees. See here in the template of the EmployeeList Component.
<h2>Employee List</h2> <search-bar [PlaceHolderText]="'Search employee...'" (Search)="OnEmployeeSearch($event)"></search-bar> <table class="table table-responsive table-bordered table-striped" myVisibility> <thead> <tr> <th>Employee ID</th> <th>Employee Name</th> <th>Contact No</th> <th>Designation</th> </tr> </thead> <tbody> <tr [class.special] = "item.EmployeeID == 1" [style.color] = "item.EmployeeID == 1? 'white':'black'" *ngFor="let item of EmployeeList;"> <td>{{item.EmployeeID}}</td> <td>{{item.FirstName}} {{item.LastName}}</td> <td>{{item.ContactNo}}</td> <td>{{item.Designation}}</td> </tr> </tbody> </table>See here in line 15, we have used *ngFor directive. Here * indicates that it's a structural directive. This is the syntax for using ngFor directive.
What it is doing here? It's adding one tr element in our DOM for each item of the array EmployeeList. And whenever we will add or remove any item in the array, a new element will be added or removed from our DOM.
Let's see what happen when we will add a new item in the EmployeeList array.
I will write code for add a new employee in our EemployeeList array inside setTimeout method So we can see is ngFor directive automatically add a new element in our DOM for the newly added item in the array or not.
import {Component, OnInit} from '@angular/core' import {EmployeeModel} from '../Models/EmployeeModel' @Component({ selector:'employee-list', templateUrl:'employee-list.component.html', moduleId: module.id }) export class EmployeeListComponent implements OnInit{ public EmployeeList: Array<EmployeeModel> = []; public TotalPageNumber:number = 5; public LoadIntialData(filterText:string):void{ this.EmployeeList = [{ EmployeeID:1, FirstName: "Sourav", LastName:"Mondal", ContactNo: "1234567890", Designation:"Software Developer" }, { EmployeeID:2, FirstName: "Rik", LastName:"Decosta", ContactNo: "1478523690", Designation:"Software Developer" },{ EmployeeID:3, FirstName: "Jhon", LastName:"Decosta", ContactNo: "5874213690", Designation:"Software Developer" }]; if(filterText !=""){ var afterFilterEmpList: Array<EmployeeModel> = []; this.EmployeeList.forEach(item=>{ if(item.FirstName.toLowerCase().includes(filterText) || item.LastName.toLowerCase().includes(filterText)) { afterFilterEmpList.push(item); } }) this.EmployeeList = afterFilterEmpList; } } ngOnInit(){ this.LoadIntialData(""); setTimeout(()=> { this.EmployeeList.push({ EmployeeID:4, FirstName: 'Riki', LastName:'Roy', ContactNo: '1245789632', Designation: 'HR' }); }, 5000); } OnEmployeeSearch(searchText):void{ this.LoadIntialData(searchText); } }Now if we run the application, you can see that a new element will be added in the table after 5 seconds.
The ngFor exports some other values also and those are...
index - return the current index of the iteration,
first - return a boolean value for identifying is the current item is the first item or not in the iteration,
last - return a boolean value for identifying is the current item is the last item or not in the iteration,
even - it also returns a boolean value for identifying is the current item is an even index,
and odd - it returns a boolean value for identifying is the current item is odd index.
We can use this values as per our needs. Like We can use the index value for showing serial no here in this table, what we do most of the time when we are showing a list of items on our web page right?
So let's see how we can do it here.
<h2>Employee List</h2> <search-bar [PlaceHolderText]="'Search employee...'" (Search)="OnEmployeeSearch($event)"></search-bar> <table class="table table-responsive table-bordered table-striped" myVisibility> <thead> <tr> <th>Serial No</th> <th>Employee ID</th> <th>Employee Name</th> <th>Contact No</th> <th>Designation</th> </tr> </thead> <tbody> <tr [class.special] = "item.EmployeeID == 1" [style.color] = "item.EmployeeID == 1? 'white':'black'" *ngFor="let item of EmployeeList;let i = index;let even=even; let odd=odd;" [ngClass]="{'even': even, 'odd' : odd}"> <td>{{i + 1}}</td> <td>{{item.EmployeeID}}</td> <td>{{item.FirstName}} {{item.LastName}}</td> <td>{{item.ContactNo}}</td> <td>{{item.Designation}}</td> </tr> </tbody> </table>Now if we run the application, we can see the serial no column is there in the table showing serial no. Also if you see inspect element you can see odd/even class added on table row as per odd/even index item.
Create a custom structural directive in Angular 2
Till now we have learned about built-in structural directives and seen how to use. Let's create a custom structural directive now.See this image, the pagination control. This is what we are going to create our custom structural directive for.
We do paging to prevent our pages from becoming too long and overwhelming. right?
When we do paging, we do have a value to knowing the total number of pages available. So we can generate the pagination control based on the total page number value.
So what we have to do is? we have to add elements in our DOM for paging based on the total page number value same as the ngFor built-in structural directive.
But can we do that using ngFor directive?
No. We can not do that directly using the ngFor directive because in the ngFor directive we have to give an array but we have a numeric value of total page number.
So what we are going to do is? we will create our own custom structural directive, where we can add/remove elements in our DOM based on a numeric value. Let's do that.
Creating directive in angular 2 is similar to creating component but here we have to decorate the class with @Directive decorator instead of @Component decorator.
Let's create it.
First of all, we have to add a typescript class for creating our custom structural directive. So here in our application, I will add a new folder "RangeRepeater" inside the src > App > Shared folder first. Then we will add a typescript class here in this RangeRepeater folder. So right click on the folder and click on New File then enter name "rangerepeater.directive.ts"
import {Directive, TemplateRef, ViewContainerRef, Input} from '@angular/core'; @Directive({ selector:'[rangeRepeater]' }) export class RangeRepeaterDirective{ constructor(private templateRef: TemplateRef<any>, private viewContainerRef: ViewContainerRef){ } @Input() set rangeRepeater(value){ for(var i:number=value[0]; i<= value[1]; i++){ this.viewContainerRef.clear(); this.viewContainerRef.createEmbeddedView(this.templateRef,{ $implicit: i }) } } }As we are creating a structural directive and using this directive we will add/remove elements in our DOM, we imported TemplateRef & ViewContainerRef from the angular core for changing our layout.
TemplateRef represents an embedded template that can be used to instantiate embedded views and ViewContainerRef represents a container where one or more views can be attached.
And 1 more thing we have injected here is Input because we will pass the total page number value here into this directive. Basically, it is the Angular way of injecting values directly into a component/directive.
See line 6, I have added a constructor method here and will add templateRef and ViewContainerRef as a parameter for getting instances of this 2.
And in line 8, I have added an input here for receive injected values what we will pass from where we will use the directive. Here in our application, we will pass this values from our EmployeeList component. We will see this later. first, let's finish this directive.
You can see I have added a setter function. We can directly add a variable here decorated with the @Input decorator for receiving the injected values but here I have used a setter function as I want to transform the values rather than using it directly.
As we have to add elements in our DOM based on the provided values. So I have added a for loop here and then inside the loop, I have created elements to add in our DOM. and As we have to use the value of "i" inside the template, we passed this with the $implicit property.
Use our custom created structural directive in our application
We will use our custom created structural directive in the EmployeeList component first. So open the template of the EmployeeList component. and here I will use it.<h2>Employee List</h2> <search-bar [PlaceHolderText]="'Search employee...'" (Search)="OnEmployeeSearch($event)"></search-bar> <table class="table table-responsive table-bordered table-striped" myVisibility> <thead> <tr> <th>Serial No</th> <th>Employee ID</th> <th>Employee Name</th> <th>Contact No</th> <th>Designation</th> </tr> </thead> <tbody> <tr [class.special] = "item.EmployeeID == 1" [style.color] = "item.EmployeeID == 1? 'white':'black'" *ngFor="let item of EmployeeList;let i = index;let even=even; let odd=odd;" [ngClass]="{'even': even, 'odd' : odd}"> <td>{{i + 1}}</td> <td>{{item.EmployeeID}}</td> <td>{{item.FirstName}} {{item.LastName}}</td> <td>{{item.ContactNo}}</td> <td>{{item.Designation}}</td> </tr> </tbody> </table> <div> <ul class="pagination pagination-lg"> <li *rangeRepeater="[1,TotalPageNumber]; let page;"> <a href="/employeelist/page/{{page}}">{{page}}</a> </li> </ul> <select class="form-control" style="width:200px"> <option *rangeRepeater="[1,TotalPageNumber]; let page;" [value]="page">{{page}}</option> </select> </div>Here you can see how we have used our custom structural directive the rangeRepeater directive.
Update app.module.ts file
Our custom directive is completed. Now we have to add the directive in our bootstrap Module in the app.module.ts file. Let me add this RangeRepeater directive in bootstrap so we can see use the directive in our application.import { NgModule } from "@angular/core"; import {BrowserModule} from "@angular/platform-browser"; import {AppComponent} from "./app.component" import {EmployeeListComponent} from './EmployeeList/employee-list.component' import {SearchbarComponent} from './Shared/SearchBar/searchbar.component' import {MyVisibilityDirective} from './Shared/MyVisibility/myvisibility.directive' import {RangeRepeaterDirective} from './Shared/RangeRepeater/range-repeater.directive' @NgModule({ imports: [BrowserModule], declarations: [AppComponent,EmployeeListComponent, SearchbarComponent, MyVisibilityDirective, RangeRepeaterDirective], // bootstrap:[AppComponent], bootstrap:[EmployeeListComponent] }) export class AppModule{ }
Run the application
For run the application go to View menu > click on Integrated Command > it will open the terminal > write npm start in the terminal and hit the enter key.Live Demo Download