ngDoCheck In Angular

ngDoCheck In Angular

In this article, we will learn how to use the ngDoCheck lifecycle hook. Using the DoCheck hook, we will learn how to keep track of changes to @Input properties. We also examine how key-value and inerrable differ in how they work.

What is ngDoCheck lifecycle hook

In the previous chapter, we looked at how the OnChanges hook works. When Angular detects a change to the data-bound input property, it fires this event.

We also looked at how OnChanges isn't fired when the input property is an array/object since Angular compares the properties using dirty checking.

When Angular fails to detect changes to the input property, we can use the DoCheck to construct our own custom change detection. After each change detection, the Angular fires the DoCheck hook.

ngDoCheck example

Let's build on the code we created in the previous OnChanges article.

add the following code under customer.ts file.

export class Customer {
    code: number;
    name: string;
}

Now, The app.component.ts file has not changed.
import { Component} from '@angular/core';
import { Customer } from './customer';
 
@Component({
  selector: 'app-root',
  template: `
        <h1>{{title}}!</h1>
        <p> Message : <input type='text' [(ngModel)]='message'> </p>
        <p> Code : <input type='text' [(ngModel)]='code'></p>
        <p> Name : <input type='text' [(ngModel)]='name'></p>
        <p><button (click)="updateCustomer()">Update </button>
        <child-component [message]=message [customer]=customer></child-component>
        ` ,
 
        styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'ngOnChanges';
  message = '';
  customer: Customer = new Customer();
  name= '';
  code= 0;
 
  updateCustomer() {
    this.customer.name = this.name;
    this.customer.code = this.code;
  }
 
}

Add the following code under the Child Component
import { Component, Input, OnChanges, OnInit, SimpleChanges, SimpleChange, DoCheck  } from '@angular/core';
import { Customer } from './customer';
 
@Component({
    selector: 'child-component',
    template: `<h2>Child  Component</h2>
               <p>Message {{ message }} </p>
               <p>Customer Name {{ customer.name }} </p>
               <p>Customer Code {{ customer.code }} </p>
               <p>Do Check count {{ DocheckCount }} </p>
               <ul><li *ngFor="let log of changelog;"> {{ log }}</li></ul> `
})
export class ChildComponent implements OnChanges, DoCheck, OnInit {
    @Input() message: string;
    @Input() customer: Customer;
    changelog: string[] = [];
    oldCustomer: Customer= new Customer();
    DocheckCount = 0;
 
    ngOnInit() {
        console.log('OnInit');
        this.oldCustomer = Object.assign({}, this.customer);
    }
 
    ngDoCheck() {
        console.log('Docheck');
        this.DocheckCount++;
        if (this.oldCustomer.name !== this.customer.name || this.oldCustomer.code !== this.customer.code ) {
            const to  = JSON.stringify(this.customer);
            const from = JSON.stringify(this.oldCustomer);
            const changeLog = `DoCheck customer: changed from ${from} to ${to} `;
            this.changelog.push(changeLog);
 
            this.oldCustomer = Object.assign({}, this.customer);
        }
    }
 
    ngOnChanges(changes: SimpleChanges) {
        console.log('OnChanges');
        console.log(JSON.stringify(changes));
 
        // tslint:disable-next-line:forin
        for (const propName in changes) {
             const change = changes[propName];
             const to  = JSON.stringify(change.currentValue);
             const from = JSON.stringify(change.previousValue);
             const changeLog = `${propName}: changed from ${from} to ${to} `;
             this.changelog.push(changeLog);
        }
    }
}

We started by importing the DoCheck function from the @angular/core library.
import { Component, Input, OnChanges, OnInit, SimpleChanges, SimpleChange, DoCheck  } from '@angular/core';
import { Customer } from './customer';

The DoCheck Interface should be implemented.
export class ChildComponent implements OnChanges, DoCheck, OnInit {

We've added a new property called oldCustomer to store the customer's previous value. We also have the DoCheckcount property, which maintains track of the number of times this hook has been called.
oldCustomer: Customer= new Customer();
DocheckCount = 0;

In the OnInit hook, we're cloning the customer object into oldCustomer. To see if the customer object has changed, the old customer values are compared to the new customer values.
ngOnInit() {
    console.log('OnInit');
    this.oldCustomer = Object.assign({}, this.customer);
}

Finally, in the ngDoChek hook, we compare new customer values to oldCustomer values to see if there are any differences.
ngDoCheck() {
    console.log('Docheck');
    this.DocheckCount++;
    if (this.oldCustomer.name !== this.customer.name || this.oldCustomer.code !== this.customer.code ) {
        const to  = JSON.stringify(this.customer);
        const from = JSON.stringify(this.oldCustomer);
        const changeLog = `DoCheck customer: changed from ${from} to ${to} `;
        this.changelog.push(changeLog);

        this.oldCustomer = Object.assign({}, this.customer);
    }
}

That concludes the discussion. When you run the application, you'll notice that every time a customer is added, our code detects the change and logs it in our changelog.

When ngDoCheck is called

It's worth noting that DoCheckCount continues to increase with each keystroke and mouse movement.

This hook is used a lot by Angular. This hook is called at the end of each change detection cycle, regardless of where the change happened.

It's best to keep Docheck implementation basic and lightweight. Otherwise, the user will have a negative experience.

Checking for changes

We cloned our customer object and examined each property for changes in the example above. But what if we're dealing with a huge object or array?

Angular provides a different service that evaluates a provided object/array and identifies what has changed.

There are two types of distinctions that angular offers.
  1. key-value differs
  2. iterable differs

key-value differs

The KeyValueDiffers service keeps track of changes to an object over time and exposes an API for reacting to such changes.

For dictionary-like structures, key-value differences should be used, and it works at the key level. When a new key is added, a key is removed, or the value of the key changes, this difference will be identified.

Iterable differs

When we have a list-like structure and we merely want to know what was added or removed from that list, we use the Iterable differs service.

If any elements are added or removed from the array, they will be detected. If changes are made to the array's elements, this will not be detected.

To do so, you'll need to construct a unique key value for each element.

Example of key-value differs

Import KeyValueDiffers from @angular/core

Now, Inject it into the constructor
constructor(private differs: KeyValueDiffers) {
}

Make a distinct property for the customer object.
differ: any;

Create an initial value for the different object.

The find() method looks for a key value that is different in each collection. If the differ is not discovered, it is created and an instance of DefaultKeyValueDiffer is returned.
ngOnInit() {
    console.log('OnInit');
    this.differ = this.differs.find(this.customer).create(null);
}

We then check if our object has changed using the diff method of the differ. If there is no change, the object returns null. It returns an object containing the object's modifications.
const customerChanges = this.differ.diff(this.customer);

Using the forEachChangedItem, forEachAddedItem, and forEachRemovedItem methods, we can determine what properties were added, changed, or removed, as shown below.
if (customerChanges) {
    console.log(customerChanges);
    customerChanges.forEachChangedItem(r =>  this.changelog.push('changed ' + r.key + ' ' + JSON.stringify( r.currentValue)));
    customerChanges.forEachAddedItem(r =>  this.changelog.push('added ' + r.key + ' ' + JSON.stringify( r.currentValue)));
    customerChanges.forEachRemovedItem(r =>  this.changelog.push('removed ' + r.key + ' ' + JSON.stringify( r.currentValue)));
}

The following is the complete child component code:
import { Component, Input, OnChanges, OnInit, SimpleChanges, SimpleChange, DoCheck, KeyValueDiffers } from '@angular/core';
import { Customer } from './customer';
 
@Component({
    selector: 'child-component',
    template: `<h2>Child  Component</h2>
               <p>Message {{ message }} </p>
               <p>Customer Name {{ customer.name }} </p>
               <p>Customer Code {{ customer.code }} </p>
               <p>Do Check count {{ DocheckCount }} </p>
               <ul><li *ngFor="let log of changelog;"> {{ log }}</li></ul> `
})
export class ChildComponent implements OnChanges, DoCheck, OnInit {
    @Input() message: string;
    @Input() customer: Customer;
    changelog: string[] = [];
    oldCustomer: Customer= new Customer();
    DocheckCount = 0;
    differ: any;
 
    constructor(private differs: KeyValueDiffers) {
 
    }
    ngOnInit() {
        console.log('OnInit');
        this.differ = this.differs.find(this.customer).create(null);
    }
 
    ngDoCheck() {
        console.log('Docheck');
        this.DocheckCount++;
 
        const customerChanges = this.differ.diff(this.customer);
 
        if (customerChanges) {
            console.log(customerChanges);
            customerChanges.forEachChangedItem(r =>  this.changelog.push('changed ' + r.key + ' ' + JSON.stringify( r.currentValue)));
            customerChanges.forEachAddedItem(r =>  this.changelog.push('added ' + r.key + ' ' + JSON.stringify( r.currentValue)));
            customerChanges.forEachRemovedItem(r =>  this.changelog.push('removed ' + r.key + ' ' + JSON.stringify( r.currentValue)));
        }
 
    }
 
    ngOnChanges(changes: SimpleChanges) {
        console.log('OnChanges');
        console.log(JSON.stringify(changes));
 
        // tslint:disable-next-line:forin
        for (const propName in changes) {
             const change = changes[propName];
             const to  = JSON.stringify(change.currentValue);
             const from = JSON.stringify(change.previousValue);
             const changeLog = `${propName}: changed from ${from} to ${to} `;
             this.changelog.push(changeLog);
        }
    }
}

Iterable differs

Iterable differ is similar to key-value differ in that it only exposes methods for objects that have been added or removed. The iterable differs from other iterators in that it works with arrays.

The use of iterable differences is similar to the use of key-value differs. Simply inject the IterableDiffers into the constructor after importing them. Except for forEachChangedItem, the rest of the code remains the same.

Conclusion

We learned how to use the ngDoCheck hook to provide custom change detection for input properties in this lesson. We also looked at how to use iterable and key-value differences.

I hope this article helps you and you will like it.👍

If you have any doubt or confusion then free to ask in comment section.

Post a Comment

Previous Post Next Post