Angular ngFor Example

By Arvind Rai, June 20, 2020
This page will walk through Angular ngFor example. Angular provides NgForOf directive with ngFor selector. It instantiates a template for every element of given iterator. The NgForOf has local variables that can be used in iteration. The local variables are index, first, last, even, odd. The ngFor is used with HTML elements as well as <ng-template>. Whenever the contents of iterator changes, ngFor performs respective changes in DOM. These changes are tracked by object identity by default. We can change tracking identify by using trackBy. We assign a user defined function to trackBy and that function will return an identity for every element of iterator. When we use trackBy with ngFor, it starts change propagation tracked by given identity and not by object identity. Using trackBy improves the performance of ngFor directive. If iterator is the instance of Observable or Promise, we need to use async pipe with ngFor directive. Here on this page we will provide complete example of ngFor with its local variables and trackBy function step by step.

Technologies Used

Find the technologies being used in our example.
1. Angular 9.1.11
2. Node.js 12.5.0
3. NPM 6.9.0

NgForOf Directive

The NgForOf directive instantiates a template for every item in an iteration. The selector of NgForOf is ngFor. According to Angular Doc, NgForOf directive has been defined as following.
@Directive({ selector: '[ngFor][ngForOf]' })
class NgForOf<T> implements DoCheck, OnChanges {
   -------
}
Find the properties of NgForOf directive.
index: Index of current item.
even: True for an even index.
odd: True for an odd index.
first: True for first item.
last: True for last item.
ngForOf: It is useful to alias when expression is more complex than a property access, for example using Async pipe such as (obsPersons | async) .

NgFor with HTML Elements

The ngFor directive is used with HTML elements as following.
<li *ngFor="let item of items; index as i; even as isEven; odd as isOdd; first as isFirst; last as isLast; trackBy: trackByFn">
  ------
</li> 

NgFor with <ng-template>

The ngFor directive is used with <ng-template> as following.
<ng-template ngFor let-item [ngForOf]="items" let-i="index" let-isEven="even" let-isOdd="odd" let-isFirst="first" let-isLast="last" [ngForTrackBy]="trackByFn">
  <li> ------ </li>
</ng-template> 

index, even and odd

Here we will use ngFor with index, even and odd local variables using <div> element.
<div *ngFor="let person of allPersons; index as i; even as isEven; odd as isOdd">
   <font color="blue" *ngIf="isEven">{{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}} </font>   
   <font color="red" *ngIf="isOdd">{{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}} </font>  
   <input [(ngModel)]="person.name"> <button (click)="remove(person.personId)">Remove</button>       
</div> 
The same can be achieved using <ng-template> as following.
<ng-template ngFor let-person [ngForOf]="allPersons" let-i="index" let-isEven="even" let-isOdd="odd">
 <div>
   <font color="blue" *ngIf="isEven">{{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}} </font>   
   <font color="red" *ngIf="isOdd">{{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}} </font>  
   <input [(ngModel)]="person.name"> <button (click)="remove(person.personId)">Remove</button>       
 </div>
</ng-template> 

first and last

Find the use of first and last local variables of ngFor using <div> element.
<div *ngFor="let person of allPersons; index as i; first as isFirst; last as isLast">
   <font color="blue" *ngIf="isFirst; else elseBlock1">{{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}} </font>  
   <ng-template #elseBlock1>   
      <font color="red" *ngIf="isLast; else elseBlock2">{{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}} </font>  
      <ng-template #elseBlock2>   
	  {{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}}
      </ng-template>	  
   </ng-template>
   <input [(ngModel)]="person.name"> <button (click)="remove(person.personId)">Remove</button>       
</div> 
We can do same using <ng-template> as following.
<ng-template ngFor let-person [ngForOf]="allPersons" let-i="index" let-isFirst="first" let-isLast="last">
 <div>
   <font color="blue" *ngIf="isFirst; else elseBlock1">{{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}} </font>  
   <ng-template #elseBlock1>   
      <font color="red" *ngIf="isLast; else elseBlock2">{{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}} </font>  
      <ng-template #elseBlock2>   
	      {{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}}
      </ng-template>	  
   </ng-template>
   <input [(ngModel)]="person.name"> <button (click)="remove(person.personId)">Remove</button>       
 </div>
</ng-template> 

NgFor with Async Pipe

Here we will use async pipe with ngFor using <li> element. Suppose we have an instance of Observable as following.
obsPersons: Observable<Person[]> 
With the above instance we can use Async pipe as following.
<li *ngFor="let person of obsPersons | async"> -- </li> 
Aliasing can be achieved in following way.
<li *ngFor="let person of obsPersons | async as persons"> 
Now find the sample code to use ngFor with async pipe with aliasing using <li> element.
<ul>
   <li *ngFor="let person of obsPersons | async as persons; index as i">
       {{i + 1}} out of {{persons.length}}: {{person.personId}} - {{person.name}} - {{person.age}}   
       <input [(ngModel)]="person.name"> <button (click)="remove(person.personId)">Remove</button>       
   </li>
</ul> 
Now we will use ngForOf with <ng-template> for async pipe.
<ng-template ngFor let-person [ngForOf]="obsPersons | async"> <li> --- </li> </ng-template> 
Aliasing of ngForOf with <ng-template> is achieved as let-persons="ngForOf". Find the code snippet.
<ng-template ngFor let-person [ngForOf]="obsPersons | async" let-persons="ngForOf"> <li> --- </li> </ng-template> 
Now find the sample code to use ngForOf with async pipe with aliasing using <li> element.
<ul>
  <ng-template ngFor let-person [ngForOf]="obsPersons | async" let-persons="ngForOf" let-i="index">
    <li>
       {{i + 1}} out of {{persons.length}}: {{person.personId}} - {{person.name}} - {{person.age}}   
       <input [(ngModel)]="person.name"> <button (click)="remove(person.personId)">Remove</button>       
    </li>
  </ng-template>      
</ul> 

trackBy

The ngFor uses trackBy for change propagation. When the contents of the iterator changes, ngFor makes the corresponding changes to DOM as given below.
1. If an item is added then new instance of template is added in DOM.
2. If an item is removed then its respective template is removed from DOM.
3. If items are reordered, respective templates are reordered in DOM.
4. If no change for an item, its respective template in DOM will be unchanged.

Angular uses object identity to maintain the entire above task of change propagation. If we want that Angular should not use object identity for change propagation but use user provided identity, then we can achieve it using trackBy . It assigns a function that accepts index and item as function arguments. We can use trackBy with HTML elements as following.
<li *ngFor="let person of allPersons; trackBy: trackByFn"> -- </li> 
trackByFn can be defined to return an identity as following.
trackByFn(index: number, item: any) {
   // return any id
} 
Now find the sample code for demo which we are using in our example with <li> element.
<ul>
   <li *ngFor="let person of allPersons; index as i; trackBy: personTrackByFn">
       {{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}}   
       <input [(ngModel)]="person.name"> <button (click)="remove(person.personId)">Remove</button>       
   </li>
</ul> 
We have defined personTrackByFn to return person id.
personTrackByFn(index: number, person: Person) {
    return person.personId;
}
If we are using <ng-template> then the same can be achieved using ngForTrackBy . Find the code snippet.
<ul>
 <ng-template ngFor let-person [ngForOf]="allPersons" let-i="index" [ngForTrackBy]="personTrackByFn">
   <li>
       {{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}}   
       <input [(ngModel)]="person.name"> <button (click)="remove(person.personId)">Remove</button>       
   </li>
 </ng-template>   
</ul> 
In case we are fetching data from server, we should certainly use trackBy or ngForTrackBy to change the default behavior (i.e. change propagation by object identity). Suppose a scenario that iterator of ngFor is produced from RPC to server and that RPC is re-run then object identity may change. So ngFor will re-draw the template even as there is no change in any object value. That impacts the performance and hence in this case we should use trackBy or ngForTrackBy function to change the default tracking id.

Complete Example

Project Structure
angular-demo
|
|--src
|   |
|   |--app 
|   |   |
|   |   |--person.ts
|   |   |--person.service.ts
|   |   |--ngforof-demo.component.ts
|   |   |--ngforof-demo.component.html
|   |   |--ng-template-ngforof.component.ts
|   |   |--ng-template-ngforof.component.html
|   |   |
|   |   |--app.module.ts
|   |   |--app.component.ts
|   |   
|   |--main.ts
|   |--index.html
|   |--styles.css
|
|--node_modules
|--package.json
person.ts
export class Person { 
	constructor(public personId:number, public name:string, public age:number) {
	}
} 
person.service.ts
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Person } from './person';

@Injectable()
export class PersonService {
  PERSONS = [
    new Person(101, 'Mahesh', 25),
    new Person(102, 'Ram', 20),  
    new Person(103, 'Krishna', 30),
    new Person(104, 'Bharat', 25)   
  ];	
  getAllPersons(): Observable<Person[]>  {
    return Observable.of(this.PERSONS);
  }
  add(name: string, age: number) {
    let maxIndex = this.PERSONS.length - 1;
    let objWithMaxIndex = this.PERSONS[maxIndex];
    let newId = objWithMaxIndex.personId + 1;		
    this.PERSONS.push(new Person(newId, name, age));
  }   
  remove(personId: number) {
    let obj = this.PERSONS.find(ob => ob.personId === personId);
    this.PERSONS.splice(this.PERSONS.indexOf(obj), 1);
  } 	
} 
ngforof-demo.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { PersonService } from './person.service';
import { Person } from './person';

@Component({
   selector: 'ngforof-demo',
   templateUrl: './ngforof-demo.component.html'
})
export class NgForOfDemoComponent implements OnInit { 
   name: string;
   age: number;
   allPersons: Person[];
   obsPersons: Observable<Person[]> 
   
   constructor(private personService: PersonService) {
   }
   ngOnInit() {
       this.personService.getAllPersons()
	      .subscribe(persons => this.allPersons = persons);
		  
       this.obsPersons = this.personService.getAllPersons();		  
   }
   add() {
       this.personService.add(this.name, this.age);
       this.name = '';
       this.age = undefined;
   }   
   remove(personId: number) {
       this.personService.remove(personId);
   }  
   personTrackByFn(index: number, person: Person) {
       return person.personId;
   }
} 
ngforof-demo.component.html
<div>
  <p><b>Add Person</b></p>
  <p>Name: <input [(ngModel)]="name">, Age: <input [(ngModel)]="age"> </p>
</div>
<button (click)="add()">ADD</button>

<br/><b>Aliasing index, even, odd </b> <br/><br/>
<div *ngFor="let person of allPersons; index as i; even as isEven; odd as isOdd">
   <font color="blue" *ngIf="isEven">{{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}} </font>   
   <font color="red" *ngIf="isOdd">{{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}} </font>  
   <input [(ngModel)]="person.name"> <button (click)="remove(person.personId)">Remove</button>       
</div>

<br/><b>Aliasing first, last </b> <br/><br/>
<div *ngFor="let person of allPersons; index as i; first as isFirst; last as isLast">
   <font color="blue" *ngIf="isFirst; else elseBlock1">{{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}} </font>  
   <ng-template #elseBlock1>   
      <font color="red" *ngIf="isLast; else elseBlock2">{{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}} </font>  
      <ng-template #elseBlock2>   
	      {{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}}
      </ng-template>	  
   </ng-template>
   <input [(ngModel)]="person.name"> <button (click)="remove(person.personId)">Remove</button>       
</div>

<b>Aliasing ngForOf </b>
<ul>
   <li *ngFor="let person of obsPersons | async as persons; index as i">
       {{i + 1}} out of {{persons.length}}: {{person.personId}} - {{person.name}} - {{person.age}}   
       <input [(ngModel)]="person.name"> <button (click)="remove(person.personId)">Remove</button>       
   </li>
</ul>

<br/><b>Using trackBy </b>
<ul>
   <li *ngFor="let person of allPersons; index as i; trackBy: personTrackByFn">
       {{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}}   
       <input [(ngModel)]="person.name"> <button (click)="remove(person.personId)">Remove</button>       
   </li>
</ul> 
ng-template-ngforof.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { PersonService } from './person.service';
import { Person } from './person';

@Component({
   selector: 'ng-template-ngforof',
   templateUrl: './ng-template-ngforof.component.html'
})
export class NgTemplateNgForOfComponent implements OnInit { 
   name: string;
   age: number;
   allPersons: Person[];
   obsPersons: Observable<Person[]> 
   
   constructor(private personService: PersonService) {
   }
   ngOnInit() {
       this.personService.getAllPersons()
	      .subscribe(persons => this.allPersons = persons);
		  
       this.obsPersons = this.personService.getAllPersons();		  
   }
   add() {
       this.personService.add(this.name, this.age);
       this.name = '';
       this.age = undefined;
   }   
   remove(personId: number) {
       this.personService.remove(personId);
   }  
   personTrackByFn(index: number, person: Person) {
       return person.personId;
   }
} 
ng-template-ngforof.component.html
<div>
  <p><b>Add Person</b></p>
  <p>Name: <input [(ngModel)]="name">, Age: <input [(ngModel)]="age"> </p>
</div>
<button (click)="add()">ADD</button>

<br/><b>Aliasing index, even, odd </b> <br/><br/>
<ng-template ngFor let-person [ngForOf]="allPersons" let-i="index" let-isEven="even" let-isOdd="odd">
 <div>
   <font color="blue" *ngIf="isEven">{{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}} </font>   
   <font color="red" *ngIf="isOdd">{{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}} </font>  
   <input [(ngModel)]="person.name"> <button (click)="remove(person.personId)">Remove</button>       
 </div>
</ng-template>

<br/><b>Aliasing first, last </b> <br/><br/>
<ng-template ngFor let-person [ngForOf]="allPersons" let-i="index" let-isFirst="first" let-isLast="last">
 <div>
   <font color="blue" *ngIf="isFirst; else elseBlock1">{{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}} </font>  
   <ng-template #elseBlock1>   
      <font color="red" *ngIf="isLast; else elseBlock2">{{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}} </font>  
      <ng-template #elseBlock2>   
	      {{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}}
      </ng-template>	  
   </ng-template>
   <input [(ngModel)]="person.name"> <button (click)="remove(person.personId)">Remove</button>       
 </div>
</ng-template>

<b>Aliasing ngForOf </b>
<ul>
  <ng-template ngFor let-person [ngForOf]="obsPersons | async" let-persons="ngForOf" let-i="index">
   <li>
       {{i + 1}} out of {{persons.length}}: {{person.personId}} - {{person.name}} - {{person.age}}   
       <input [(ngModel)]="person.name"> <button (click)="remove(person.personId)">Remove</button>       
   </li>
  </ng-template>      
</ul>

<br/><b>Using ngForTrackBy </b>
<ul>
 <ng-template ngFor let-person [ngForOf]="allPersons" let-i="index" [ngForTrackBy]="personTrackByFn">
   <li>
       {{i + 1}}: {{person.personId}} - {{person.name}} - {{person.age}}   
       <input [(ngModel)]="person.name"> <button (click)="remove(person.personId)">Remove</button>       
   </li>
 </ng-template>   
</ul> 
app.component.ts
import { Component } from '@angular/core';

@Component({
   selector: 'app-root',
   template: `
         <ngforof-demo></ngforof-demo>
         <!--ng-template-ngforof></ng-template-ngforof-->		 
   `
})
export class AppComponent { 
} 
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

import { AppComponent }  from './app.component';
import { NgForOfDemoComponent } from './ngforof-demo.component';
import { NgTemplateNgForOfComponent } from './ng-template-ngforof.component';
import { PersonService } from './person.service';

@NgModule({
  imports: [     
        BrowserModule,
	FormsModule
  ],
  declarations: [
        AppComponent,
	NgForOfDemoComponent,
	NgTemplateNgForOfComponent
  ],
  providers: [
        PersonService
  ],
  bootstrap: [
        AppComponent
  ]
})
export class AppModule { }

Run Application

To run the application, find following steps.
1. Download source code using download link given below on this page.
2. Use downloaded src in your angular CLI application. To install angular CLI, find the link.
3. Run ng serve using command prompt.
4. Now access the URL http://localhost:4200
Find the print screen of the output.
Angular ngFor Example

References

NgForOf Directive
Angular <ng-template> Example

Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI







©2024 concretepage.com | Privacy Policy | Contact Us