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 // falseBut 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
- https://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html
- https://blog.thoughtram.io/angular/2016/02/01/zones-in-angular-2.html
- http://teropa.info/blog/2015/03/02/change-and-its-detection-in-javascript-frameworks.html
- https://blog.thoughtram.io/angular/2016/01/22/understanding-zones.html
Angular Change Detection
By Peter Malina
Angular Change Detection
- 361