Angular Performance: Your App at the Speed of Light

Angular Performance: Your App at the Speed of Light

In this talk, you’ll get to know common performance pitfalls in Angular applications and learn how to avoid them. We’ll have a look at different change detection strategies and ChangeDetectorRef, Zone.js and NgZone. Let’s make your Angular app blazing fast!

12c88a3a10478fa18d0363b3ba3d9df1?s=128

Christian Liebel

August 30, 2019
Tweet

Transcript

  1. Angular Performance Your App at the Speed of Light Christian

    Liebel @christianliebel Consultant
  2. Don’t over-optimize. Angular Performance Your App at the Speed of

    Light First Rule
  3. Angular Performance Your App at the Speed of Light Change

    Detection Basics
  4. Hi Angular! Basics // app.component.html <h1>Hi {{ title }}!</h1> //

    app.component.ts @Component({ /* … */ }) export class AppComponent { title = 'Angular'; } Angular Performance Your App at the Speed of Light Change Detection
  5. Basics // app.component.html <h1>Hi {{ title }}!</h1> <button (click)="update()"> Update

    </button> // app.component.ts @Component({ /* … */ }) export class AppComponent { title = 'Angular'; update() { this.title = 'Foo'; } } Angular Performance Your App at the Speed of Light Change Detection Hi Foo! Hi Angular! Update
  6. Basics Change detection… - is the magical part of Angular

    that makes data binding “just work” - is a very handy feature that helps a lot, but it can also work against you - is strongly related to Angular application performance Angular Performance Your App at the Speed of Light Change Detection
  7. Component Tree AppComponent NavComponent ContentComponent ListComponent Angular Performance Your App

    at the Speed of Light Change Detection
  8. Change Detector Tree AppComponent NavComponent ContentComponent ListComponent Angular Performance Your

    App at the Speed of Light Change Detection AppComponent CD NavComponent CD ContentComponent CD ListComponent CD CHANGE
  9. Change Detector detectChanges() Called when an event has occured and

    bindings should be checked Angular Performance Your App at the Speed of Light Change Detection this.title = 'Foo'; <h1>Hi {{ title }}!</h1>
  10. Per default, each change in your application leads to… -

    A single CD cycle - From top to bottom (all components) - Unidirectional (no cycles allowed) Angular Performance Your App at the Speed of Light Change Detection DEMO
  11. Angular Performance Your App at the Speed of Light

  12. First findings Reduce duration of a change detection cycle -

    Reduce amount of bindings (e.g. grids: virtual scrolling via CDK) - Avoid binding to (computationally intensive) getters or functions Keep CD cycle < 16 ms! Angular Performance Your App at the Speed of Light Change Detection
  13. Profiling // main.ts platformBrowserDynamic().bootstrapModule(AppModule).then(module => enableDebugTools(module.injector.get(ApplicationRef).components[0])); Execute ng.profiler.timeChangeDetection() to measure

    the duration of a change detection run (500ms or 5 change detection cycles) Angular Performance Your App at the Speed of Light Change Detection DEMO
  14. Angular Performance Your App at the Speed of Light

  15. Angular Performance Your App at the Speed of Light Zone.js

    & NgZone
  16. How to detect a change? AppComponent NavComponent ContentComponent ListComponent Angular

    Performance Your App at the Speed of Light Zone.js AppComponent CD NavComponent CD ContentComponent CD ListComponent CD CHANGE
  17. A look at Angular’s dependencies "dependencies": { "@angular/common": "~8.2.3", "rxjs":

    "~6.4.0", "zone.js": "~0.9.1" }, Zone.js Your App at the Speed of Light Angular Performance
  18. A Meta-Monkey Patch Zone.js setTimeout setInterval geolocation.getCurrentPosition XMLHttpRequest PromiseRejectionEvent requestAnimationFrame

    click focus mousemove addEventListener Your App at the Speed of Light Angular Performance
  19. Execution Context Debugging Pending asynchronous tasks are known Profiling Measuring

    performance (Google Web Tracing Framework) Mocking/Testing Hooks beforeTask, … Zone.js Your App at the Speed of Light Angular Performance
  20. NgZone Angular Performance Your App at the Speed of Light

    Zone.js current (global) zone NgZone Angular boot
  21. NgZone NgZone catches asynchronous operations from the Angular app When

    no tasks are remaining for the current VM turn, the NgZone will trigger a change detection cycle (tick) Angular Performance Your App at the Speed of Light Zone.js NgZone setTimeout setInterval onclick Detect changes Detect changes Detect changes
  22. Change Detection Trigger Angular Performance Your App at the Speed

    of Light Zone.js NgZone.onMicrotaskEmpty ApplicationRef.tick() view1.detectChanges() view2.detectChanges() viewN.detectChanges()
  23. @Injectable() export class ApplicationRef { // … constructor (/* …

    */) { this._zone.onMicrotaskEmpty .subscribe( {next: () => { this._zone.run(() => { this.tick(); }); }}); } // … tick(): void { // … for (let view of this._views) { view.detectChanges(); } // … } // … } Angular Performance Your App at the Speed of Light https://github.com/angular/angular/blob/master/packages/core/src/application_ref.ts
  24. Common Pitfalls Long CD cycles in combination with high-frequency events

    - mousemove - scroll - requestAnimationFrame - setInterval with short intervals (clocks!) Angular Performance Your App at the Speed of Light Zone.js DEMO
  25. Angular Performance Your App at the Speed of Light

  26. NgZone Zone.js current (global) zone NgZone mousemove Detect changes mousemove

    Detect changes mousemove Detect changes mousemove Detect changes Your App at the Speed of Light Angular Performance
  27. NgZone Opt-Out constructor (ngZone: NgZone) { ngZone.runOutsideAngular(() => { //

    runs outside Angular zone, for performance-critical code ngZone.run(() => { // runs inside Angular zone, for updating view afterwards }); }); } Zone.js Your App at the Speed of Light Angular Performance ! View and model can get out of sync!
  28. NgZone Zone.js current (global) zone NgZone mousemove mousemove mousemove mousemove

    Your App at the Speed of Light Angular Performance DEMO
  29. Angular Performance Your App at the Speed of Light

  30. Disable Patches (polyfills.ts) (window as any).__Zone_disable_requestAnimationFrame = true; // disable

    patch requestAnimationFrame (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames Angular Performance Your App at the Speed of Light Zone.js ! View and model can get out of sync!
  31. Disable Zone (= disable async change detection!) platformBrowserDynamic().bootstrapModule(AppModule, { ngZone:

    'noop' }); constructor(applicationRef: ApplicationRef) { applicationRef.tick(); // trigger CD yourself } Angular Performance Your App at the Speed of Light Zone.js ! View and model can get out of sync!
  32. Angular Performance Your App at the Speed of Light Change

    Detection Strategies
  33. Overview Default Uses Zone.js for detecting changes and updates bindings

    OnPush Restricts change detection to changes of @Input parameters Angular Performance Your App at the Speed of Light Change Detection Strategies AppComponent CD NavComponent CD ContentComponent CD ListComponent CD AppComponent CD NavComponent CD ContentComponent CD ListComponent CD OnPush
  34. OnPush <my-component [foo]="bar"> </my-component> @Component({ selector: 'my-component', template: '{{ foo

    }}', changeDetection: ChangeDetectionStrategy.OnPush }) export class MyComponent { @Input() public foo: string; } Change detection only reacts to changes of @Input parameters Angular compares the values passed to an @Input parameter (newValue === oldValue). If you are passing objects, make sure to pass in new instances! Angular Performance Your App at the Speed of Light Change Detection Strategies ! View and model can get out of sync! DEMO
  35. Angular Performance Your App at the Speed of Light

  36. Angular Performance Your App at the Speed of Light Change

    Detector
  37. OnPush & Detecting Changes What to do if a component

    changes unrelated to an @Input parameter? constructor(private dataService: DataService) {} ngOnInit() { this.dataService.updates$ .subscribe(newData => this.data = newData); // no update! } Angular Performance Your App at the Speed of Light Change Detector
  38. ChangeDetectorRef constructor(cdRef: ChangeDetectorRef) {} A reference to the ChangeDetector of

    your component - detectChanges() - markForCheck() - detach() - checkNoChanges() - reattach() Angular Performance Your App at the Speed of Light Change Detector
  39. markForCheck() Explicitly marks a component as dirty/changed (when using OnPush)

    Angular Performance Your App at the Speed of Light Change Detector AppComponent CD NavComponent CD ContentComponent CD ListComponent CD DIRTY AppComponent CD NavComponent CD ContentComponent CD ListComponent CD OnPush
  40. markForCheck() constructor(private dataService: DataService, private cdRef: ChangeDetectorRef) {} ngOnInit() {

    this.dataService.updates$.subscribe(newData => { this.data = newData; this.cdRef.markForCheck(); }); } Angular Performance Your App at the Speed of Light Change Detector
  41. Angular Performance Your App at the Speed of Light Async

    Pipe
  42. Overview Takes observables or promises {{ data$ | async }}

    Waits for the observable to emit/promise to resolve and then displays the value Angular Performance Your App at the Speed of Light Async Pipe
  43. Advantages For observables: - Async Pipe subscribes for you -

    Async Pipe takes care of unsubscribing from the observable - Async Pipe calls markForCheck for each update – perfect match for OnPush! Angular Performance Your App at the Speed of Light Async Pipe
  44. Code @Injectable() @Pipe({name: 'async', pure: false}) export class AsyncPipe implements

    OnDestroy, PipeTransform { // … private _updateLatestValue(async: any, value: Object): void { if (async === this._obj) { this._latestValue = value; this._ref.markForCheck(); } } } Angular Performance Your App at the Speed of Light Async Pipe https://github.com/angular/angular/blob/master/packages/common/src/pipes/async_pipe.ts
  45. Simplifying OnPush // component.ts data$: Observable<string>; constructor(dataService: DataService) { this.data$

    = this.dataService.updates$; } // component.html {{ data$ | async }} Angular Performance Your App at the Speed of Light Async Pipe
  46. Detaching from Change Detection Angular Performance Your App at the

    Speed of Light
  47. Detaching Components changeDetector.detach(); changeDetector.reattach(); Angular Performance Your App at the

    Speed of Light Change Detector AppComponent CD NavComponent CD ContentComponent CD ListComponent CD AppComponent CD NavComponent CD ContentComponent CD ListComponent CD ! View and model can get out of sync!
  48. Local Change Detection constructor(cdRef: ChangeDetectorRef) { cdRef.detach(); // detaches this

    view from the CD tree // cdRef.detectChanges(); // detect this view & children // cdRef.reattach(); } Angular Performance Your App at the Speed of Light Change Detector
  49. Findings Reduce amount of change detection cycles - Disable Zone.js

    (not a good idea in most cases) - Opt-out of NgZone (for operations that should not affect bindings) - Disable Zone.js patches (in case you can’t opt-out, e.g. 3rd party libs) - ChangeDetectionStrategy.OnPush (good default, but be careful) - Local change detection via ChangeDetectorRef (for the few components that do not have to respond to changes from outside) Angular Performance Your App at the Speed of Light Change Detector
  50. Runtime Performance 1. Don’t over-optimize 2. Reduce duration of a

    change detection cycle - Reduce amount of bindings - Avoid binding to (computationally intensive) getters or functions 3. Reduce amount of change detection cycles - Disable zone - NgZone - Zone.js patches - ChangeDetectorRef - ChangeDetectionStrategy Angular Performance Your App at the Speed of Light Cheat Sheet
  51. Thank you for your kind attention! Christian Liebel @christianliebel christian.liebel@thinktecture.com