Angular ngDoCheck()

By Arvind Rai, February 13, 2024
Angular ngDoCheck() is a callback method that performs custom change-detection. The ngDoCheck() is the method of DoCheck interface. The DoCheck is a lifecycle hook that invokes a custom change-detection function for a directive. The Angular classes NgClass, NgForOf, NgStyle, NgSwitchCase and UpgradeComponent implement DoCheck interface to perform change-detection. Angular also performs default change-detection an invokes ngOnChanges() callback which is the method of OnChanges lifecycle hook. The ngDoCheck() callback method is run after ngOnChanges() callback method. According to Angular doc, we should not use both ngDoCheck() and ngOnChanges() callback methods to respond to changes on the same input. To perform change-detection with ngDoCheck(), we need to use KeyValueDiffers and IterableDiffers for implementing custom change checking for collections. The KeyValueDiffers can detect the changes in Array as well as changes in properties of objects whereas IterableDiffers can only detect changes in iterable objects such as Array.
To use ngDoCheck(), our components need to implement DoCheck. Find the code snippet.
import { Component, DoCheck } from '@angular/core';

@Component({
    ------ 
})
export class EmployeeComponent implements DoCheck {
  ngDoCheck() {
    
  }
} 
Here on this page we will create examples of ngDoCheck() with KeyValueDiffers class to detect changes in Array while adding and removing elements and to detect changes in property value of objects of Array. We will also create IterableDiffers class example to detect changes in Array.

1. ngDoCheck() with KeyValueDiffers

KeyValueDiffers is the repository of different map diffing strategies. Angular uses it internally for directives NgClass, NgStyle etc. The KeyValueDiffers can detect the changes in Array as well as changes in properties of objects. Find the steps to detect changes using KeyValueDiffers.
1. KeyValueDiffers can be injected into component using constructor.
constructor(private kvDiffers: KeyValueDiffers) {} 
2. KeyValueDiffers provides find method.
find(kv: any): KeyValueDifferFactory 
kv: Pass the object that needs to be detected for changes.
find method returns KeyValueDifferFactory.

3. KeyValueDifferFactory provides factory for KeyValueDiffer. To get KeyValueDiffer instance, KeyValueDifferFactory provides create method.
create<K, V>(): KeyValueDiffer<K, V> 
Find the sample code to get instance of KeyValueDiffer.
this.arrayDiffer = this.kvDiffers.find(this.empArray).create(); 
Here arrayDiffer is the instance of KeyValueDiffer.

4. KeyValueDiffer is a differ that tracks changes made to an object over time. It has a diff method to compute a difference between the previous state and the new object state.
diff(object: { [key: string]: V; }): KeyValueChanges<string, V> | null 
We need to pass the object containing the new value and it returns KeyValueChanges.
let empArrayChanges = this.arrayDiffer.diff(this.empArray); 
Here empArrayChanges is the instance of KeyValueChanges.

5. KeyValueChanges keeps the changes in the map since the last time the method diff was invoked. KeyValueChanges has following methods.
forEachItem: Iterates over all changes.
forEachPreviousItem: Iterates over previous items that has changed.
forEachChangedItem: Iterates those items whose value is changed.
forEachAddedItem: Iterates over all added items.
forEachRemovedItem: Iterates over all removed items.
All the above methods of KeyValueChanges provides KeyValueChangeRecord item in iteration.

6. KeyValueChangeRecord is the record representing the item change information. KeyValueChangeRecord has properties such as currentValue and previousValue. To get current value, we call its currentValue property and to get previous value, we call its previousValue property. Find the code snippet.
if (empArrayChanges) {
  empArrayChanges.forEachAddedItem((record) => {
	let emp = record.currentValue;
	console.log('Added ' + emp.name);
  });
} 

1.1 Detect Changes in Array with KeyValueChanges

Suppose we have an array of Employee class and we want to detect changes in the array whenever an item is added or removed. We can detect changes using KeyValueDiffers as following.
@Input() empArray: Employee[];
constructor(private kvDiffers: KeyValueDiffers) {}
ngOnInit() {
  this.arrayDiffer = this.kvDiffers.find(this.empArray).create();
}
ngDoCheck() {
    const empArrayChanges = this.arrayDiffer.diff(this.empArray);
    if (empArrayChanges) {
      empArrayChanges.forEachAddedItem((record) => {
        let emp = record.currentValue;
        console.log('Added ' + emp.name);
      });
      empArrayChanges.forEachRemovedItem((record) => {
        let emp = record.previousValue;
        console.log('Removed ' + emp.name);
      });
    }
} 
The above code will not detect changes if any Employee object of the array changes value of its any property.

1.2 Detect Changes in Objects inside Array with KeyValueChanges

Suppose we have an array of Employee class and we want to detect changes when any Employee object of the array changes value of its any property. We can detect changes using KeyValueDiffers as following.
@Input() empArray: Employee[];
empDifferMap = new Map<number, any>();
empMap = new Map<number, Employee>();
arrayDiffer: any;
constructor(private kvDiffers: KeyValueDiffers) {}
ngOnInit() {
    this.empArray.forEach(emp => {
      this.empDifferMap[emp.id] = this.kvDiffers.find(emp).create();
      this.empMap[emp.id] = emp;
    })
}
ngDoCheck() {
    for (let [key, empDiffer] of this.empDifferMap) {
      let empChanges = empDiffer.diff(this.empMap.get(key));
      if (empChanges) {
        empChanges.forEachChangedItem(record => {
          console.log('Previous value: ' + record.previousValue);
          console.log('Current value: ' + record.currentValue);
        });
      }
    }
} 

2. ngDoCheck() with IterableDiffers: Detect Changes in Array

The IterableDiffers is the repository of different iterable diffing strategies used by directives NgFor, NgClass etc. The IterableDiffers can only detect changes in iterable objects such as Array. Find the steps to detect changes using IterableDiffers.
1. IterableDiffers can be injected into component using constructor.
constructor(private itrDiffers:IterableDiffers) {} 
2. IterableDiffers provides find method.
find(iterable: any): IterableDifferFactory 
find method returns IterableDifferFactory.

3. IterableDifferFactory provides factory for IterableDiffers. To get IterableDiffer instance, IterableDifferFactory provides create method.
create<V>(trackByFn?: TrackByFunction<V>): IterableDiffer<V> 
Find the sample code to get instance of IterableDiffer.
this.empDiffer = this.itrDiffers.find([]).create(null); 
Here empDiffer is the instance of IterableDiffer.

4. IterableDiffer is a differ that tracks changes made to an iterable object over time. It has a diff method to compute a difference between the previous state and the new object state.
diff(object: NgIterable<V>): IterableChanges<V> | null 
We need to pass the object containing the new value and it returns IterableChanges.
const empArrayChanges = this.empDiffer.diff(this.empArray); 
Here empArrayChanges is the instance of IterableChanges.

5. IterableChanges is an object describing the changes in the iterable collection since last time diff() was invoked. IterableChanges has following methods.
forEachItem: Iterates over all changes.
forEachOperation: Iterates over a set of operations.
forEachPreviousItem: Iterates over previous items that has changed.
forEachAddedItem: Iterates over all added items.
forEachMovedItem: Iterates over all moved items.
forEachRemovedItem: Iterates over all removed items.
forEachIdentityChange: Iterate over all items which had their identity changed.
All the above methods of IterableChanges provides IterableChangeRecord item in iteration.

6. IterableChangeRecord is the record representing the item change information. IterableChangeRecord has properties such as currentIndex, previousIndex, item and trackById. To get value, we call its item property.

7. Find the code snippet to detect changes in Array. Suppose we have an array of Employee class and we want to detect changes in the Array whenever an item is added or removed.
@Input()
empArray: Employee[];

empDiffer: any;

constructor(private itrDiffers:IterableDiffers) {
}
ngOnInit() {
    this.empDiffer = this.itrDiffers.find([]).create(null);
}
ngDoCheck() {
    const empArrayChanges = this.empDiffer.diff(this.empArray);
    if (empArrayChanges) {
      empArrayChanges.forEachAddedItem(record => {
        let emp = record.item;
        console.log('Added ' + emp.name);
      });
      empArrayChanges.forEachRemovedItem(record => {
        let emp = record.item;
        console.log('Removed ' + emp.name);
      });
    }
} 


3. Complete Example

employee.ts
export class Employee {
	constructor(public id: number, public name: string){}
} 
app.component.ts
import { Component, OnInit } from '@angular/core';

import { Employee } from './employee';

@Component({
  selector: 'app-root',
  template: `
    <h2>ngDoCheck() Demo</h2>
    <table border="1" cellpadding="8" cellspacing="0">
    <tr *ngFor="let emp of empArray; let i = index">
       <td>{{emp.id}}</td><td>{{emp.name}}</td><td><button (click)="remove(i)">Remove</button></td>
       <td><button (click)="update(i)">Update</button></td> 
    </tr>
    </table>     
    <button (click)="add()">Add</button>
    <h3>Change Logs</h3>
    <table border="1" cellpadding="8" cellspacing="0">
       <tr>
         <td><b>Using KeyValueDiffers</b></td>
         <td><b>Using IterableDiffers</b></td>
       </tr>
       <tr>
         <td><app-emp-kv [empArray]="empArray"></app-emp-kv></td>
         <td><app-emp-itr [empArray]="empArray"></app-emp-itr></td>
       </tr>       
    </table>
  `
})
export class AppComponent implements OnInit {
  empArray = [];
  index = 103;
  ngOnInit() {
    this.empArray.push(new Employee(100, "Mahesh"));
    this.empArray.push(new Employee(101, "Krishna"));
    this.empArray.push(new Employee(102, "Shiva"));
  }
  add() {
    this.empArray.push(new Employee(this.index, "Name"+ this.index++));
    console.log('Employee added: ' + JSON.stringify(this.empArray));
  }
  remove(index) {
    console.log(index);
    this.empArray.splice(index, 1);
  }
  update(index) {
    this.empArray[index].name += "-U";
  }
} 
employee-kvdiff.component.ts
import { Component, DoCheck, KeyValueDiffers, OnInit, Input } from '@angular/core';

import { Employee } from './employee';

@Component({
  selector: 'app-emp-kv',
  template: `
    <div *ngFor="let log of kvChangeLogs">
      {{log}}
    </div> 
  `
})
export class EmployeeKVDiffComponent implements DoCheck, OnInit {
  @Input()
  empArray: Employee[];

  empDifferMap = new Map<number, any>();
  empMap = new Map<number, Employee>();
  arrayDiffer: any;
  kvChangeLogs: string[] = [];
  
  constructor(private kvDiffers: KeyValueDiffers) {
  }
  ngOnInit() {
    this.arrayDiffer = this.kvDiffers.find([]).create();

    this.empArray.forEach(emp => {
      this.empDifferMap[emp.id] = this.kvDiffers.find(emp).create();
      this.empMap[emp.id] = emp;
    })
  }
  ngDoCheck() {
    //Detect changes in array when item added or removed
    const empArrayChanges = this.arrayDiffer.diff(this.empArray);
    if (empArrayChanges) {
      empArrayChanges.forEachAddedItem((record) => {
        let emp = record.currentValue;
        this.empDifferMap.set(emp.id, this.kvDiffers.find(emp).create());
        this.empMap.set(emp.id, emp);
        this.kvChangeLogs.push('Added ' + emp.name);

      });
      empArrayChanges.forEachRemovedItem((record) => {
        let emp = record.previousValue;
        this.empDifferMap.delete(emp.id);
        this.empMap.delete(emp.id);
        this.kvChangeLogs.push('Removed ' + emp.name);
      });
    }

    //Detect changes in object inside array
    for (let [key, empDiffer] of this.empDifferMap) {
      const empChanges = empDiffer.diff(this.empMap.get(key));
      if (empChanges) {
        empChanges.forEachChangedItem(record => {
          this.kvChangeLogs.push('---Update (id=' + key + ')---');
          this.kvChangeLogs.push('Previous value: ' + record.previousValue);
          this.kvChangeLogs.push('Current value: ' + record.currentValue);
          this.kvChangeLogs.push('-------------------------');
        });
      }
    }
  }
} 
employee-itrdiff.component.ts
import { Component, DoCheck, IterableDiffers, OnInit, Input } from '@angular/core';

import { Employee } from './employee';

@Component({
  selector: 'app-emp-itr',
  template: `
    <div *ngFor="let log of itrChangeLogs">
      {{log}}
    </div> 
  `
})
export class EmployeeITRDiffComponent implements DoCheck, OnInit {
  @Input()
  empArray: Employee[];

  itrChangeLogs: string[] = [];
  empDiffer: any;

  constructor(private itrDiffers: IterableDiffers) {
  }
  ngOnInit() {
    this.empDiffer = this.itrDiffers.find([]).create(null);
  }
  ngDoCheck() {
    const empArrayChanges = this.empDiffer.diff(this.empArray);
    if (empArrayChanges) {
      empArrayChanges.forEachAddedItem(record => {
        let emp = record.item;
        console.log('Added ' + emp.name);
        this.itrChangeLogs.push('Added ' + emp.name);
      });
      empArrayChanges.forEachRemovedItem(record => {
        let emp = record.item;
        console.log('Removed ' + emp.name);
        this.itrChangeLogs.push('Removed ' + emp.name);
      });
    }
  }
} 
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 { EmployeeITRDiffComponent } from './employee-itrdiff.component';
import { EmployeeKVDiffComponent } from './employee-kvdiff.component';

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

4. Output

Find the print-screen of the output.
Angular ngDoCheck()

5. References

Angular doc: DoCheck
Angular doc: KeyValueDiffers
Angular doc: IterableDiffers
Angular Lifecycle Hooks

6. Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI
LEARN MORE








©2024 concretepage.com | Privacy Policy | Contact Us