OnPush change detection strategy  in Angular

Wojciech Trawiński

YouGov Front-end meeting, 2021

Angular performance tips

Angular performance tips

  • OnPush change detection strategy,
  • modules and components lazy-loading,
  • taking control over change detection process,
  • pure pipes,
  • memoization,
  • setters vs ngOnChanges

Change detection

Change detection

NgZone

Change detection

Default strategy

NgZone

Change detection

Default strategy

OnPush strategy

NgZone

Change detection

Default strategy

OnPush strategy

NgZone

Asynchronous events

OnPush strategy triggerers

OnPush strategy triggerers

  • Input property change (compared by reference),
  • event dispatched in the DOM sub-tree (with listener),
  • markForCheck method's call (ChangeDetectorRef).

Immutability

Immutability

mutable update pattern

interface User {
  name: string;
  age: number;
}

...

public user: User = { name: "Max", age: 30 };

...

public changeUserAge(age: number): void {
  this.user.age = age;
}

Immutability

mutable update pattern

immutable update pattern

interface User {
  name: string;
  age: number;
}

...

public user: User = { name: "Max", age: 30 };

...

public changeUserAge(age: number): void {
  this.user.age = age;
}
interface User {
  name: string;
  age: number;
}

...

public user: User = { name: "Max", age: 30 };

...

public changeUserAge(age: number): void {
  this.user = { ...this.user, age };
}

Asynchronous events

Asynchronous events

timers

public date: string = null;
private dateInterval = null;

...

public showDate(): void {
  this.dateInterval = setInterval(() => {
    this.date = new Date().toISOString();
  }, 1000);
}

Asynchronous events

timers

XHR

public date: string = null;
private dateInterval = null;

...

public showDate(): void {
  this.dateInterval = setInterval(() => {
    this.date = new Date().toISOString();
  }, 1000);
}
public remoteData: string = null;

...

public loadData(): void {
  mockResponse$.subscribe(res => {
    this.remoteData = res;
  });
}

AsyncPipe

AsyncPipe

  • automatically (un)subscribes to(from) stream,
  • handles stream's reference change,
  • calls the markForCheck method,
  • does not apply distinctUntilChanged operator.

AsyncPipe

  • automatically (un)subscribes to(from) stream,
  • handles stream's reference change,
  • calls the markForCheck method,
  • does not apply distinctUntilChanged operator.

AsyncPipe

export function select<T, Props, K>(
  pathOrMapFn: ((state: T, props?: Props) => any) | string,
  propsOrPath?: Props | string,
  ...paths: string[]
) {
  return function selectOperator(source$: Observable<T>): Observable<K> {
    let mapped$: Observable<any>;

    ...

    return mapped$.pipe(distinctUntilChanged());
  };
}

with NgRx selectors

ngDoCheck method

ngDoCheck method

  • top-level component with OnPush strategy,
  • called frequently,
  • manual checks and the markForCheck method call.

ngDoCheck method

  • top-level component with OnPush strategy,
  • called frequently,
  • manual checks and the markForCheck method call.

Other gotchas

Other gotchas

  • events outside of the NgZone,
  • custom form controls,
  • binding updates is a part of the change detection process.

OnPush by default

OnPush by default

{
  ...
  
  "schematics": {
    "@schematics/angular:component": {
      "changeDetection": "OnPush"
    }
  },
    
  ...
}

angular.json

Conclusions

Conclusions

  • favour immutable update patterns,
  • subscribe using the AsyncPipe,
  • prevent unnecessary updates (distinctUntilChanged),
  • call markForCheck/detectChanges method as the last resort,
  • bindings get updated as a part of the change detection.
Made with Slides.com