Pranay Rana: 2017

Sunday, December 17, 2017

Dynamic Html Structure with - *ngIf, *ngFor & ngSwitch

Directives in Angular Js 2 is used for structuring html document(DOM) (web page) and for changing behavior of html elements (Html page element like <input>, <form> etc.). Following kind of directive provided by angular js 2
  • Component – is most commonly used and most of the developer aware about it from the first day of development. One cannot start learning Angular js without learning it. It is only directive which comes with template attached with it. @Compnent is metadata value by which developer can make mark created directive is component. More at: https://angular.io/guide/displaying-data
  • Attribute – is directive which can change behavior of the element (html tag, component), with which is get attached as attribute. Most common attribute directive is ngModel, this allows bind element with the backend typescript object property, and provide (oneway-twoway) binding between model and view. @Directive is metadata value by which developer can make mark created directive is attibute directive. More at : https://angular.io/guide/attribute-directives
  • Structure – is type of directive which can change structure of html document. Directives are capable of adding-removing elements in html document. @Compnent is metadata value by which developer can make mark created directive is structure directive. More at : https://angular.io/guide/structural-directives
Angular js not only provide some build in directives but also provide way to create custom directives , find more at : https://angular.io/.

Not going in much details of directives , how to build them as post is focused on three most important structural directives *ngFor, *ngIf and ngSwitch & how to use them to build dynamic structure. Before jumping in lets understand this directives one by one

*ngIf – is in build directive which allows to show or hide element on page (i.e. to control display of element).

Example:
<div *ngIf=”IsShow” class="alert alert-danger"> Display Error!!! </div>
In example, IsShow is component model property which value get controlled by logic. For example : IsShow=true then is error and div get displayed or IsShow=false then is no error and div get hidden.

*ngFor – is in build directive to display element repeatedly on html page. It basically for loop on html document to create similar element on page but with different value and it works with Arrays of object (object can be user-defined class object or in build type object).

Example:
AppComponent.ts
export class AppComponent  {
  items:Array<number> = new Array<number>(1,2,3);
 }
Component.html
<ul>
  <li *ngFor="let item of items">
    {{item}}
  </li>
</ul>



<div *ngFor="let item of items">
    {{item}}
</div>



<select>
  <option *ngFor="let item of items" value="item">
    {{item}}
  </option>
</select>



In above example *ngFor works with Array of number and display all number repeating element. In 1. Case it displays list of 3 elements by repeating li element under ul, in 2. Case it displays 3 div element by repeatedly, In 3. Case it displays 3 option in select list.

Note:
*ngFor can decrease performance of your UI if not handled correctly.  Because *ngFor works with arrays and if any element removed from array or added in array, *ngFor remove all created element from DOM (html page) and recreate it with the new values.
So to avoid this behavior Angular team provided trackBy to this method to *ngFor directive to track changes.  It works as below

Example :
If model is like this

Employee.ts
export class Employee
{
  Id:number;
  Name:string;
  Designation: string;
  Department:string;
}
Then your html component html will be like as below with trackBy

AppComponent.html
<div *ngFor="let emp of employees; trackBy: trackEmployeeById">
  ({{emp.id}}) {{emp.name}}
</div>

And in your component.ts you have to add method for trackBy like as below.

AppComponent.ts
trackEmployeeById (index: number, emp: Employee): number { return emp.id; }
By adding trackBy Angular Js track element and do not recreate full structure when element get removed or added in array.

Index : like for loop *ngFor also allows access to index of element, one can access it as below
<div *ngFor="let item of items; let i=index">{{i + 1}} - {{item}}</div>
ngSwitch – is directive which is similar to switch statement in programming construct, it only emits element in Html page (DOM) when condition is matched.

Example:
AppComponent.ts
export class AppComponent  {

  items:Array<number> = new Array<number>(1,2,3,4);

 }
Component.Html 
 <div *ngFor="let item of items">
  <div ngSwitch="{{item}}">
    <div *ngSwitchCase="'1'">
      display 1 
    </div>
    <div *ngSwitchCase="'2'">
      display 2
    </div>
    <div *ngSwitchDefault>
      {{item}}-- display other element
    </div>
  </div>
</div>
As you see in above html, ngSwitch is similar to switch statement in programming language, similar to switch …case it also has ngSwitchCase. So when above html get executed then for array value 1 , it display 1 , value 2 it display 2 , for value 3 & 4 it displays  “3—display other element” & “4—display other element”.

Important note:
Important point to not here is, one cannot apply two Structural directives on one html element like as done in following code.
<div *ngIf="items.length>0" *ngFor="let item of items">
    {{item}}
</div>
So when above get executed following error get displayed on console of browser. Which says multiple template binding on one element.

It can be easily get resolved by doing as following
<div *ngIf="items.length>0">
  <div *ngFor="let item of items">
    {{item}}
  </div>
</div>
<ng-container>
But problem with above is one more div element get added in Html page (DOM). So to avoid it angular js provided <ng-container> element , which you can use as below

<ng-container *ngIf="items.length>0">
  <div *ngFor="let item of items">
    {{item}}
  </div>
</ng-container>
Now there is no extra div element get added on html page(DOM).  <ng-container> doesn’t affect structure of html page it used by angular js only just to note that it container which is having group of element under it.

Dynamic Structure with Structure Directives
Above is very simple and easy example with the structural directives, and it shows how you can easily achieve things in html without using line of javascript code, which developers are doing without angular js.
Now question may come how complex dynamic structure one can create with help of the structural directives ? to understand lets consider below example :

AppComponent.ts
export class AppComponent  {
  newTmpArr = [
    ["567", "2017-04-17T14:22:48", "45000", "button"],
    ["568", "2017-04-17T14:22:48", "45000", "button"],
    ["569", "2017-04-17T14:22:48", "45000", "link"],
    ["570", "2017-04-17T14:22:48", "45000", "button"]
  ];
 
  DoWork(ele:any)
  {
    //here you can do work , this get 
     //called when you press "click" button in table 
    alert(ele);
  }
}
For example, consider above data received by application component from external source like json File or from web service call.
Now requirement is as following
  1. Create html Table structure using this data structure
  2. Where ever property value is “button” html button needs to be get created &  when value is “link” html hyper link needs to be get created , and display other values as is as text element in html.
  3. Created link or button element must call function ”DoWork() (defined in script file above)” in script to process further.
Now if think of javascript, to achieve table structure with given data how many line of code needed. But in angular it can be done very easily

Component.Html
<table>
  <thead>
    <tr>
      <th>ID</th>
      <th>Date</th>
      <th>Ammount</th>
      <th>Action</th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let ele of newTmpArr; trackBy: trackByIds">
      <td *ngFor="let val of ele">
        <ng-container ngSwitch="{{val}}">
          <ng-container  *ngSwitchCase="'button'">
            <input type="button" (click)="DoWork(ele)" value="click" />
          </ng-container>
          <ng-container *ngSwitchCase="'link'">
            <a href="#" (click)="DoWork(ele)">click</a>
          </ng-container>
          <ng-container *ngSwitchDefault>
            {{val}}
          </ng-container>
        </ng-container>
      </td>
    </tr>
  </tbody>
</table>
It display below output in html page

When button get click it display as below