Angular ChangeDetectionStrategy.OnPush

By Arvind Rai, 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

The ChangeDetectionStrategy 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

The ChangeDetectorRef 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 and ngModel. 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
    });
  }
} 
person.service.ts
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()
    );
  }
} 
person.component.ts
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>();
} 
student.component.ts
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) {
  }
} 
person.ts
export interface Person {
    name?: String;
    age?: number;
} 
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 { 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 { } 
Find the print screen of the output.
Angular ChangeDetectionStrategy.OnPush Example

Reference

Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI
LEARN MORE







©2024 concretepage.com | Privacy Policy | Contact Us