$30 off During Our Annual Pro Sale. View Details »

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!

Christian Liebel
PRO

August 30, 2019
Tweet

More Decks by Christian Liebel

Other Decks in Programming

Transcript

  1. Angular Performance
    Your App at the Speed of Light
    Christian Liebel
    @christianliebel
    Consultant

    View Slide

  2. Don’t over-optimize.
    Angular Performance
    Your App at the Speed of Light
    First Rule

    View Slide

  3. Angular Performance
    Your App at the Speed of Light
    Change Detection Basics

    View Slide

  4. Hi Angular!
    Basics
    // app.component.html
    Hi {{ title }}!
    // app.component.ts
    @Component({ /* … */ })
    export class AppComponent {
    title = 'Angular';
    }
    Angular Performance
    Your App at the Speed of Light
    Change Detection

    View Slide

  5. Basics
    // app.component.html
    Hi {{ title }}!

    Update

    // 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

    View Slide

  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

    View Slide

  7. Component Tree
    AppComponent
    NavComponent ContentComponent
    ListComponent
    Angular Performance
    Your App at the Speed of Light
    Change Detection

    View Slide

  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

    View Slide

  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'; Hi {{ title }}!

    View Slide

  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

    View Slide

  11. Angular Performance
    Your App at the Speed of Light

    View Slide

  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

    View Slide

  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

    View Slide

  14. Angular Performance
    Your App at the Speed of Light

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  20. NgZone
    Angular Performance
    Your App at the Speed of Light
    Zone.js
    current (global) zone
    NgZone
    Angular boot

    View Slide

  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

    View Slide

  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()

    View Slide

  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

    View Slide

  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

    View Slide

  25. Angular Performance
    Your App at the Speed of Light

    View Slide

  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

    View Slide

  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!

    View Slide

  28. NgZone
    Zone.js
    current (global) zone
    NgZone
    mousemove
    mousemove
    mousemove
    mousemove
    Your App at the Speed of Light
    Angular Performance
    DEMO

    View Slide

  29. Angular Performance
    Your App at the Speed of Light

    View Slide

  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!

    View Slide

  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!

    View Slide

  32. Angular Performance
    Your App at the Speed of Light
    Change Detection Strategies

    View Slide

  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

    View Slide

  34. OnPush


    @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

    View Slide

  35. Angular Performance
    Your App at the Speed of Light

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  41. Angular Performance
    Your App at the Speed of Light
    Async Pipe

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  45. Simplifying OnPush
    // component.ts
    data$: Observable;
    constructor(dataService: DataService) {
    this.data$ = this.dataService.updates$;
    }
    // component.html
    {{ data$ | async }}
    Angular Performance
    Your App at the Speed of Light
    Async Pipe

    View Slide

  46. Detaching from Change Detection
    Angular Performance
    Your App at the Speed of Light

    View Slide

  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!

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  51. Thank you
    for your kind attention!
    Christian Liebel
    @christianliebel
    [email protected]

    View Slide