NashTech Insights

On Push change detection in Angular Part-2

Piyush Agrawal
Piyush Agrawal
Table of Contents
Angular OnPush Change Detection

Hello , everyone welcome back to my next blog of Angular Change detection. So in my previous bolg Let’s Deep Down To Change Detection In Angular Part-1, we have learn basics of angular change detection. In this blog we are going to learn how can we implement OnPush change detection in our application, How can we reduce change detection cycles and optimize the performance of Our Application.

So before starting with OnPush Change detection. Let’s learn the type of change detection in Angular

Types of Change Detection in Angular

We have two types of change detection

  1. Default
  2. On Push

In the Default strategy, whenever any data to @Input() decorated properties are changed, Angular runs the change detector to rerender the view.

In the onPush strategy, Angular runs change detector only when a new reference is passed to the @Input() decorated properties or we explicitly tell angular to update view by using ChangeDetectionRef.

Let us understand by having a look at ChildComponent:

import { Component, Input } from '@angular/core';

@Component({

  selector: 'app-child',

  template: `
                   <ul>

                       <li *ngFor="let item of data">{{ item }}</li>

                  </ul>`
 
})
export class ChildComponent {

  @Input() data: string[];

}

The ChildComponent has one @Input() decorated property data, which accepts data from the parent component. Also, the ChildComponent is used inside AppComponent.

import { Component } from '@angular/core';

@Component({

  selector: 'app-root',

  template: `

               <input #input type="text" placeholder="Enter a new item">

              <button (click)="addItem(input.value)">Add Item</button>
 
            <app-child [data]="items"></app-child>
`,
})
export class AppComponent {

  items = ['Apple', 'Cake', 'Banana'];

  additem(item: string): void {

    this.items.push(item);

  }

}

In above code, we are adding item in items array from AppComponet and passing the array to ChildComponent.

Let’s compile and visit the application in a browser, we should observe an unordered list containing Apple, Cake, and Banana.

Typing to the input field and clicking the Add Item button will append the new item to the list.

The child component is updated when Angular detects the data has changed in the parent component.

Now, let’s set the change detection strategy in the child component to OnPush:

OnPush Change Detection Strategy:

import { Component, Input, ChangeDetectionStrategy } from '@angular/core';

@Component({

  selector: 'app-child',

  template:  `
                   <ul>

                       <li *ngFor="let item of data">{{ item }}</li>

                  </ul>`,

  changeDetection: ChangeDetectionStrategy.OnPush


})
export class ChildComponent {

  @Input() data: string[];

}

Let’s recompile and visit the application in a browser, you should observe an unsorted list containing Apple, Cake, and Banana.

Let’s try to add new item in list, adding a new item does not seem to append it to the unordered list.

The new data still gets pushed into the items array in the parent component, but Angular does not recognize a new reference for the data input and therefore it does not run change detection on the component.

To pass a new reference to the data input, you can replace Array.push with the spread syntax (...) in items Array:

additem(item: string): void {

    this.items = [ ...items , item];

  }

Let’s recompile and visit the application in a browser, you should observe an unsorted list containing Apple, Cake, and Banana.

Add new item in list by input field and you will we able to see , new added item reflecting on application.

This concludes modifying a sample parent and child component to use the OnPush change detection strategy.

Let’s Explore ChangeDetectionRef

When using a change detection strategy of OnPush, other than making sure to pass new references every time something should change, you can also make use of the ChangeDetectorRef for complete control.

ChangeDetectorRef.detectChanges()

You could for example keep mutating your data, and then have a button in the child component with a Refresh button.

This will require reverting items to use Array.push:

additem(item: string): void {

    this.items.push(item)

  }

Let’s Add Refresh Button in Child Component inside template below list.

@Component({

  selector: 'app-child',

  template:  `
                   <ul>

                       <li *ngFor="let item of data">{{ item }}</li>

                  </ul>

                 <button (click)="refresh()">Refresh</button>`,

  changeDetection: ChangeDetectionStrategy.OnPush

})

Now modify the TS file of ChildComponent , add ChangeDetectorRef

import {

  Component,

  Input,

  ChangeDetectionStrategy,

  ChangeDetectorRef

} from '@angular/core';

@Component({

  selector: 'app-child',

  template:  `
                   <ul>

                       <li *ngFor="let item of data">{{ item }}</li>

                  </ul>

                 <button (click)="refresh()">Refresh</button>`,

  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent {

  @Input() data: string[];

  constructor(private cd: ChangeDetectorRef) {}

  refresh() {

    this.cd.detectChanges();

  }

}

let’s again recompile and visit the application in a browser, we will observe an unordered list containing  Apple, Cake, and Banana.

Adding new items to the array does not update the unordered list.

Pressing the Refresh button will run change detection on the component and an update will be performed.

ChangeDetectorRef.markForCheck() :

Some times we have data input as an Observable.

Let’s take an example of RxJS BehaviorSubject

import { Component } from '@angular/core';

import { BehaviorSubject } from 'rxjs';

@Component({ 

  selector: 'app-root',

  template: `
               <input #input type="text" placeholder="Enter a new item">

              <button (click)="addItem(input.value)">Add Item</button>
 
            <app-child [data]="items"></app-child>
`
})
export class AppComponent {

  items = new BehaviorSubject(['Apple', 'Cake', 'Banana']);

  addItem(item) {

    this.items.next([item]);
  }

}

Now Subscribe to the Observable in Child Component so we will items.

import {
  Component,

  Input,

  ChangeDetectionStrategy,

  ChangeDetectorRef,

  OnInit

} from '@angular/core';

import { Observable } from 'rxjs';


@Component({

  selector: 'app-child',

  template: '<ul>

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

                  </ul>'

  changeDetection: ChangeDetectionStrategy.OnPush

})
export class ChildComponent implements OnInit {

  @Input() data: Observable<any>;

  items: string[] = [];

  constructor(private cd: ChangeDetectorRef) {}

  ngOnInit() {

    this.data.subscribe(item => {

      this.items = [...this.items, ...item]

    });

  }

}

Let’s compile and visit the application , application in the initial phase.

Add Item in list but we are not able to see new item because , Angular not running change detection cycle we need to call markForCheck() then Angular run change Detection Cycle and update the view.

ngOnInit() {

  this.data.subscribe(item => {

    this.items = [...this.items,  ...item];

    this.cd.markForCheck();

  });

}

Again recompile and perform all steps you will able to see changes.

There is two more methods of ChangeDetectionRef, those are ChangeDetectorRef.detach() and ChangeDetectorRef.reattach().

By these two methods you can manually attach and remove ChangeDetector from your component.

I want, you will try these two methods yourself and learn more.

If you phase any problem, You can connect us on our LinkedIn page- FrontEnd Competency.

Conclusion:

OnPush ChangeDetection Strategy is very helpfull the run change detector on our will. It reduce no of cycle of change detection and improve the performance of our application.

By Default angular run change detector in all component but by implementing OnPush help to run change detector in necessary component.

Finally, for more such posts, please follow our LinkedIn page- FrontEnd Competency.

Leave a Comment

Your email address will not be published. Required fields are marked *

Suggested Article

%d bloggers like this: