Angular Change Detection

Peter Malina

FlowUp Academy Trainings

What is change detection?

  • Detects changes in DOM to mirror in JS(TS)
  • Detects changes in JS(TS) to mirror in DOM

Model

DOM

Accessing DOM is expensive!

  • Data Reference checking (Angular)
  • Virtual DOM diff (React)
  • Immutable checking (Usable anywhere)

How Angular knows about changes?

  • Whenever Angular Zone triggers (WTF? Zone?)
  • Zone triggers basically on
    • DOM events: clicks, submits, hovers, ...
    • Zoned async calls (HTTP)
    • Timers: setTimeout, setInterval

Zone

  • Execution context (async)
  • Zone finish triggers ApplicationRef.tick() (dirty check)
  • Monkey-patches async calls
  • Builds async stack traces (Zones are why you can see nice stack traces within Angular)
  • Low overhead in production (stack trace monitors are noops)

ApplicationRef Example

// very simplified version of actual source
class ApplicationRef {

  changeDetectorRefs:ChangeDetectorRef[] = [];

  constructor(private zone: NgZone) {
    this.zone.onTurnDone
      .subscribe(() => this.zone.run(() => this.tick());
  }

  tick() {
    this.changeDetectorRefs
      .forEach((ref) => ref.detectChanges());
  }
}

Change detector

  • Each Component has it's own
  • tick() detects changes on all components
  • Results in ineffective change detection
    • Components that haven't changed are triggered
  • Ineffective in most use-cases even though Angular generates VM friendly code

Change

OnPush Change Detection

  • Triggers only when @Input's of the component change
  • Limits the change detection triggers on component sub-trees

Change

@Component({
  template: `{{data}}`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
class MyComponent {
  @Input() data;
}

Shallow change detection

  • Change Detector has no idea if the reference remains unchanged
  • There are multiple ways to do this:
    • Do it manually (Object.assign, array immutable operators (e.g. [].map()))
    • Use library such as Immutable.js
var peter = Immutable.map({
  name: 'Peter'
});

var arbelt = peter.set('name', 'Arbelt');

peter === arbelt // false

But what about Observables?

@Component({
  template: '{{counter}}',
  changeDetection: ChangeDetectionStrategy.OnPush
})
class CounterComponent {
  @Input() counterStream: Observable<any>;
  counter = 0;

  ngOnInit() {
    this.counterStream.subscribe(() => {
      this.counter++;
    })
  }
}

Manually triggering

dirty-check

@Component({
  template: '{{counter}}',
  changeDetection: ChangeDetectionStrategy.OnPush
})
class CounterComponent {
  @Input() counterStream: Observable<any>;
  counter = 0;

  constructor(private cd: ChangeDetectorRef) {}

  ngOnInit() {
    this.counterStream.subscribe(() => {
      this.counter++;
      this.cd.markForCheck();
    })
  }
}

Questions?

References

Angular Change Detection

By Peter Malina

Angular Change Detection

  • 361