Home Angular Tutorials How to use ngDoCheck lifecycle hook in angular ?

How to use ngDoCheck lifecycle hook in angular ?

by aouidane.med.amine
55,193 views 20 min read

In this tutorial, we are going to learn how to use the ngDoCheck lifecycle hook in angular.

We will also see how to keep track of the changes through the component Inputs using the ngDoCheck callback.

This will lead us to learn the difference between the ngDoCheck lifecycle hook and the ngOnChanges that we have seen in the previous tutorial.

Table of Contents

Angular Lifecycle Hooks

Angular has 8 lifecycle hooks, but in this tutorial, we will cover only the ngDoCheck lifecycle hook

What is ngDoCheck lifecycle hook ?

ngDoCheck is a callback that is used to implement our custom change detection.

If we check the documentation ,we will see that many directives are implementing the ngDoCheck lifecycle hook to perform custom change detection.

We can mention :  NgClass, NgForOf, NgStyle, NgSwitchCase

To correctly use the ngDoCheck lifecycle hook , we need to use IterableDiffers and KeyValueDiffers.

KeyValueDiffers : is used to detect changes in the object properties and the changes in the array at the same time.

IterableDiffers : is used only to check the changes in iterable objects ( Array ..)

ngDocheck is a simple method that exists in the doCheck Interface , that’s why we need to implement the doCheck interface in our component to correctly use the ngDoCheck lifecycle hook.

The ngDoCheck lifecycle hook  callback is triggered before the component is being checked. that allow us to perform custom logic and then mark component and it’s children for check in the next change detection cycle.

The ngDoCheck lifecycle hook callback is immediately fired when angular detects any changes of the data-bound input property.

The ngDoCheck callback runs just after the ngOnChanges callback using a different change detection system.

According to the doc ,it is recommended to use only one of them in your component because they are both working with the component input properties.

Let’s see in the next step , the difference between the ngDoCheck and ngOnChanges.

The difference between ngDoCheck and ngOnChanges

In Angular , we can handle the change detection using two different lifecycle hooks : the ngOnchanges and the ngDoCheck lifecycle hook.

So why does angular provide two lifecycle hooks if both of them are doing the same job? 

when we use ngOnChanges lifecycle hook , angular will perform the default change detection

That’s why , In some cases, the ngOnChanges is not enough to detect all changes because it uses the property reference to check the difference, so it can’t detect the changes if you update the object property.

Meanwhile, the ngDoCheck lifecycle hook allows us to perform our custom change detection so we can bypass the ngOnChange lifecycle hook limitations.

So , if angular fails to detect changes using the ngOnChanges you can use the ngDoCheck lifecycle hook to detect them.

Check this article speaking with more details about the difference between ngOnChanges and ngDoCheck lifecycle hooks.

How to use the ngDoCheck lifecycle hook ?

Now we are going to make an example to learn how to use the ngDoCheck lifecycle hook step by step.

As we said in the previous sections, ngDoCheck and ngOnChanges callbacks are fired when angular detects any changes of the data-bound input property. 

So that we need to create a parent component and pass values to the child component using the input data-bound.

Parent component :

				
					import {Component, OnInit} from '@angular/core';
import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms";

export class User {
  pseudo: string | undefined ;
  firstName: string| undefined ;
  lastName: string| undefined ;
  email: string | undefined ;
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent implements OnInit {
  
  userForm: FormGroup = new FormGroup({});
  userData: User = new User();

  constructor(private formBuilder: FormBuilder) {
  }

  ngOnInit() {
    this.initializeForm();
  }

  initializeForm(): void {
    this.userForm = this.formBuilder.group({
      pseudo: new FormControl('', Validators.required),
      firstName: new FormControl('', Validators.required),
      lastName: new FormControl('', Validators.required),
      email: new FormControl('', Validators.required),
    });
  }

  saveUser(): void {
    this.userData = this.userForm.getRawValue();
  }

}

				
			
				
					<!--app-component.html-->

<form [formGroup]="userForm" (ngSubmit)="saveUser()">
  <mat-card>
    <section>
      <mat-form-field>
        <mat-label>Pseudo</mat-label>
        <input class="label-input" formControlName="pseudo"  matInput type="text">
      </mat-form-field>

      <mat-form-field>
        <mat-label>FirstName</mat-label>
        <input class="label-input" formControlName="firstName"  matInput type="text">
      </mat-form-field>

      <mat-form-field>
        <mat-label>LastName</mat-label>
        <input class="label-input" formControlName="lastName"  matInput type="text">
      </mat-form-field>

      <mat-form-field>
        <mat-label>email</mat-label>
        <input class="label-input" formControlName="email"  matInput type="text">
      </mat-form-field>
    </section>

    <button type="submit">Save</button>
  </mat-card>
</form>

<app-child [user] = userData></app-child>


				
			

Now we will create the child component and we pass the user object from the parent.

child component :

				
					import {Component, DoCheck, Input, OnInit} from "@angular/core";


@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
})
export class ChildComponent implements OnInit ,DoCheck{

  @Input() user: User | undefined;

  changelogList : Array<string> = [];
  oldUser: User | undefined;

  constructor() {
  }

  ngOnInit() {
    this.oldUser = Object.assign({}, this.user);
  }

  ngDoCheck() {
    if (this.oldUser?.pseudo !== this.user?.pseudo) {
      const previous = JSON.stringify(this.oldUser);
      const current  = JSON.stringify(this.user);
      this.changelogList.push(`changes detected from ${previous} to ${current}`);

      this.oldUser = Object.assign({}, this.user);
    }
  }

}

				
			
				
					<!--child-component.html-->

<span>User Info :</span>
<span>pseudo : {{ user?.pseudo }}</span>
<span>firstName : {{ user?.firstName }}</span>
<span>lastName : {{ user?.lastName }}</span>
<span>email : {{ user?.email }}</span>
<span> Change Log :</span>
<ul><li *ngFor="let logMessage of changelogList"> {{ logMessage }}</li></ul>

				
			

Now, if we run the example , we will get something like this : 

ngDoCheck_example

In the previous example , everything is working well but unfortunately we didn’t use the ngDoCheck callback in the best way.

Since we know that the ngDoCheck callback is called very frequently by angular, we just checked and compared the current value and the last value of the Input using local objects.

So , to correctly use the ngDoCheck , we can use key-value differs and iterable differs services to perform custom change detections.

ngDoCheck using Key-value differs

In this section we are going to use the key-value differs service to handle the change detection.

So we will just update the child component :

1- Inject the service into the constructor

				
					  constructor(private differs: KeyValueDiffers) {
  }

				
			

2- Create the differ and init the property inside the ngOnInit lifecycle hook.

				
					  differ: any;

  ngOnInit() {
    this.differ = this.differs.find(this.user).create();
  }
				
			

3- Use the diff method inside ngDoCheck callback to check if the user object is changed.

the diff method returns the changes object or null ( if there are no changes )

the changes object provides multiples methods such as

forEachItem, forEachPreviousItem, forEachChangedItem, forEachAddedItem, forEachRemovedItem.

				
					//child-component.ts
import {Component, DoCheck, Input, KeyValueDiffers, OnInit} from "@angular/core";

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
})
export class ChildComponent implements OnInit, DoCheck {

  @Input() user: any;

  changelogList: Array<string> = [];

  differ: any;

  constructor(private differs: KeyValueDiffers) {
  }

  ngOnInit() {
    this.differ = this.differs.find(this.user).create();
  }


  ngDoCheck() {
    const userChanges = this.differ.diff(this.user);
    if (userChanges) {
      userChanges.forEachChangedItem((changeRecord: any) => {
        this.changelogList.push('item changed : ' + changeRecord.key + ' ' + JSON.stringify(changeRecord.currentValue))
      });
      userChanges.forEachAddedItem((changeRecord: any) => {
        this.changelogList.push('item added : ' + changeRecord.key + ' ' + JSON.stringify(changeRecord.currentValue))
      });
    }
  }
}

				
			

References

Recapitulation

In this tutorial , we have seen how to use the ngDoCheck lifecycle hook to perform change detection using two different ways.

The first way is using a manual check of the changes of the data-bound input property using the local variables and the second way that i recommend is using the key-value differs and iterable differs services that provides multiple advanced methods to check object changes.

Brief :

Clone the project

To download and run the example, please follow the steps :

1- Clone the source code from XperTuto git repository using the following command : 

git clone https://github.com/www-xperTuto-com/ngDoCheck_lifecycle_hook.git

2- Install the node dependencies using NPM command line  : npm install

3- Run the application using the following command : ng serve 

4- Access to the project through the URL : http://localhost:4200 

Read More About Angular Lifecycle hooks:

You may also like