Angular ChangeDetectionStrategy.OnPush Example
May 19, 2023
On this page we will learn to use ChangeDetectionStrategy.OnPush
in our Angular application. When we change the component data, there is change in the view. The ChangeDetectionStrategy
is the strategy when to detect the changes in data and update the view. By default, for every change in data, there is change in view because of CheckAlways
strategy. This behaviour can be changed by using OnPush
that applies CheckOnce
strategy. The ChangeDetectionStrategy.OnPush
is configured in component and applies to its child components.
ChangeDetectionStrategy
TheChangeDetectionStrategy
is the strategy to detect changes and to update data in DOM to change the view. Suppose we have an array, list, string or other primitive datatype variables in our component and their values are changing with the time. The role of ChangeDetectionStrategy
is detect the changes and update their values in DOM.
The
ChangeDetectionStrategy
has following strategies.
1. Default : The
CheckAlways
strategy is the default strategy in which for every change in values of properties in the component, there will be change in DOM and consequently the view will be up-to-date. If no detection strategy is set, the default strategy is activated.
2. OnPush : It uses the
CheckOnce
strategy and applies to all child directives and cannot be overridden. The default strategy CheckAlways
is deactivated. Using ChangeDetectorRef.markForCheck()
, we can explicitly mark the view as changed so that it can be checked again to update the view.
The
OnPush
strategy is configured in component as following.
@Component({ ...... changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent {}
Note: Starting from Angular 14, NgModel changes are reflected in the UI for
OnPush
components.
ChangeDetectorRef
TheChangeDetectorRef
is base class to handle change detection. A change-detection tree is created and collects all views that need to be checked for changes.
Find the
ChangeDetectorRef
methods to handle change detection.
1. markForCheck() : Marks the view as changed. It is useful for
OnPush
change detection strategy.
2. detach() : Detaches this view from the change-detection tree.
3. detectChanges() : Checks this view and its children for changes.
4. checkNoChanges() : Checks for no change in views. If it finds changes, then it throws error.
5. reattach() : Re-attaches the previously detached view to the change detection tree.
Using ChangeDetectionStrategy.OnPush
In our example, we have parent-child components. From the parent component, we are passing an array to child components using property binding andngModel
. The array is being updated with the time. By default when the change in array, there is change in view.
Now in the parent component, we configure
OnPush
detection strategy and hence default CheckAlways
strategy is deactivated and CheckOnce
strategy is activated. Due this this, when change in array, there is no change in view unless we call ChangeDetectorRef.markForCheck()
method.
Now find the complete code of our example.
app.component.ts
import { Component, ChangeDetectorRef, OnInit, ChangeDetectionStrategy } from "@angular/core"; import { List } from "immutable"; import { Person } from "./person"; import { PersonService } from "./person.service"; @Component({ selector: 'app-root', template: ` <app-person [allPersons]="persons"></app-person> <app-student [ngModel]="persons"></app-student> `, changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent implements OnInit { persons = List<Person>(); constructor( private changeDetection: ChangeDetectorRef, private service: PersonService ) { } ngOnInit() { this.service.getPersons().subscribe(data => { this.persons = this.persons.push(data); this.changeDetection.markForCheck(); //updates the DOM }); } }
import { Injectable } from '@angular/core'; import { Observable, of } from 'rxjs'; import { delay, map, concatAll } from 'rxjs/operators'; import { Person } from "./person"; @Injectable() export class PersonService { getPersons(): Observable<Person> { return of( { name: 'Mohit', age: 25 }, { name: 'Krishn', age: 30 }, { name: 'Shiv', age: 35 } ).pipe( map(data => of(data).pipe(delay(1000))), concatAll() ); } }
import { Component, Input } from "@angular/core"; import { List } from "immutable"; import { Person } from "./person"; @Component({ selector: 'app-person', template: ` <h3>Person Details</h3> <div *ngFor="let p of persons"> <p>{{p.name}} - {{p.age}}</p> </div> ` }) export class PersonComponent { @Input('allPersons') persons = List<Person>(); }
import { Component, forwardRef, Provider } from "@angular/core"; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; import { Person } from "./person"; const CUSTOM_VALUE_ACCESSOR: Provider = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => StudentComponent), multi: true } @Component({ selector: 'app-student', template: ` <h3>Student Details</h3> <div *ngFor="let std of students"> <p>{{std.name}} - {{std.age}}</p> </div> `, providers: [CUSTOM_VALUE_ACCESSOR] }) export class StudentComponent implements ControlValueAccessor { students: Person[] = []; writeValue(obj: any) { this.students = obj; } registerOnChange(fn: any) { } registerOnTouched(fn: any) { } setDisabledState(isDisabled: boolean) { } }
export interface Person { name?: String; age?: number; }
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { StudentComponent } from './student.component'; import { PersonComponent } from './person.component'; import { PersonService } from './person.service'; @NgModule({ imports: [ BrowserModule, FormsModule ], declarations: [ AppComponent, PersonComponent, StudentComponent ], providers: [ PersonService ], bootstrap: [AppComponent] }) export class AppModule { }
