$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, we’ll have a look at common performance pitfalls in Angular applications and how to avoid them. We’ll have a look at different change detection strategies and ChangeDetectorRef, Zone.js and NgZone, lazy loading, server-side rendering and Service Worker support. Let’s make your Angular app blazing fast!

Christian Liebel
PRO

July 04, 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. Hello, it’s me.
    Angular Performance
    Your App at the Speed of Light
    Christian Liebel
    Follow me:
    @christianliebel
    Blog:
    christianliebel.com
    Email:
    christian.liebel
    @thinktecture.com
    Cross-Platform &
    Serverless

    View Slide

  3. Runtime Performance
    Bundling
    Lazy
    Loading
    Preloading
    Strategies
    Server-Side
    Rendering
    Service
    Worker
    Angular Performance
    Your App at the Speed of Light
    Agenda
    Change
    Detection
    Basics
    Zone.js &
    NgZone
    Change
    Detection
    Strategies
    Change
    Detector
    Async Pipe
    Load Time Performance

    View Slide

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

    View Slide

  5. In general: Reduce required computations during runtime (calculations,
    painting, layouting)
    Not covered: CSS/JS tweaks, performance metrics, …
    Today: Angular-specific performance topics
    Angular Performance
    Your App at the Speed of Light
    Runtime Performance

    View Slide

  6. Runtime Performance
    Bundling
    Lazy
    Loading
    Preloading
    Strategies
    Server-Side
    Rendering
    Service
    Worker
    Angular Performance
    Your App at the Speed of Light
    Agenda
    Change
    Detection
    Basics
    Zone.js &
    NgZone
    Change
    Detection
    Strategies
    Change
    Detector
    Async Pipe
    Load Time Performance

    View Slide

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

  8. 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

  9. 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

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

    View Slide

  11. 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

  12. 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

  13. 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

  14. 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

  15. 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

  16. Runtime Performance
    Bundling
    Lazy
    Loading
    Preloading
    Strategies
    Server-Side
    Rendering
    Service
    Worker
    Angular Performance
    Your App at the Speed of Light
    Agenda
    Change
    Detection
    Basics
    Zone.js &
    NgZone
    Change
    Detection
    Strategies
    Change
    Detector
    Async Pipe
    Load Time Performance

    View Slide

  17. 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

  18. A look at Angular’s dependencies
    "dependencies": {
    "@angular/common": "~7.2.0",
    "core-js": "^2.5.4",
    "rxjs": "~6.3.3",
    "zone.js": "~0.8.26"
    },
    Zone.js
    Your App at the Speed of Light
    Angular Performance

    View Slide

  19. 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

  20. 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

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

    View Slide

  22. 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

  23. Change Detection Trigger
    https://github.com/angular/angular/blob/master/packages/core/src/application_ref.ts
    Angular Performance
    Your App at the Speed of Light
    Zone.js
    NgZone.onMicrotaskEmpty ApplicationRef.tick()
    view1.detectChanges()
    view2.detectChanges()
    viewN.detectChanges()

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

  26. 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

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

    View Slide

  28. 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

  29. 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

  30. Runtime Performance
    Bundling
    Lazy
    Loading
    Preloading
    Strategies
    Server-Side
    Rendering
    Service
    Worker
    Angular Performance
    Your App at the Speed of Light
    Agenda
    Change
    Detection
    Basics
    Zone.js &
    NgZone
    Change
    Detection
    Strategies
    Change
    Detector
    Async Pipe
    Load Time Performance

    View Slide

  31. 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

  32. 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

  33. 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

  34. 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

  35. 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

  36. 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

  37. 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

  38. 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

  39. 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

  40. Runtime Performance
    Bundling
    Lazy
    Loading
    Preloading
    Strategies
    Server-Side
    Rendering
    Service
    Worker
    Angular Performance
    Your App at the Speed of Light
    Agenda
    Change
    Detection
    Basics
    Zone.js &
    NgZone
    Change
    Detection
    Strategies
    Change
    Detector
    Async Pipe
    Load Time Performance

    View Slide

  41. 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

  42. 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!
    https://github.com/angular/angular/blob/master/packages/common/src/pipes/async_pipe.ts
    Angular Performance
    Your App at the Speed of Light
    Async Pipe

    View Slide

  43. 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

  44. Not covered:
    - HTTP/2, compression, …
    Today:
    - Reduce initial load (size & computation)
    - Reduce perceived loading time
    - Prevent downloading the same resource again
    Angular Performance
    Your App at the Speed of Light
    Load Time Performance

    View Slide

  45. Runtime Performance
    Bundling
    Lazy
    Loading
    Preloading
    Strategies
    Server-Side
    Rendering
    Service
    Worker
    Angular Performance
    Your App at the Speed of Light
    Agenda
    Change
    Detection
    Basics
    Zone.js &
    NgZone
    Change
    Detection
    Strategies
    Change
    Detector
    Async Pipe
    Load Time Performance

    View Slide

  46. The Problem
    Angular’s development directory
    structure is hard to
    • deploy
    • serve
    • cache
    • …
    Lots of files, lots of requests
    Angular and its dependencies are large
    in size, apps use only a fragment
    Bundling
    Your App at the Speed of Light
    Angular Performance

    View Slide

  47. The Problem
    Just-in-Time compilation (JiT)
    - Slow, client-side rendering
    - Compiler is 1.2 MB large in size
    - Template errors detected at runtime only
    - Potentially dangerous (injection attacks)
    Bundling
    Your App at the Speed of Light
    Angular Performance

    View Slide

  48. The Problem
    Goal: Angular app
    - with all components pre-compiled
    - combined in a single (or few) file(s)
    - without redundant/unused code
    - uglified, compressed
    Bundling
    Your App at the Speed of Light
    Angular Performance

    View Slide

  49. JiT Compilation
    Bundling
    Component
    @Component({ … })
    class UserComponent {
    user = { name: 'Chris' };
    }
    Template
    hello {{ user.name }}
    Server Client
    Component
    @Component({ … })
    class UserComponent {
    user = { name: 'Chris' };
    }
    Template
    hello {{ user.name }}
    View Class
    var v = this.comp.user.name;
    Your App at the Speed of Light
    Angular Performance

    View Slide

  50. AoT Compilation
    Bundling
    Component
    @Component({ … })
    class UserComponent {
    user = { name: 'Chris' };
    }
    Template
    hello {{ user.name }}
    Server Client
    Component
    @Component({ … })
    class UserComponent {
    user = { name: 'Chris' };
    }
    View Class
    var v = this.comp.user.name;
    Template
    hello {{ user.name }}
    View Class
    var v = this.comp.user.name;
    Your App at the Speed of Light
    Angular Performance

    View Slide

  51. ng build --prod
    AoT
    +
    Tree Shaking: “walks the dependency graph, top to bottom, and shakes
    out unused code like dead needles in a Christmas tree.”
    +
    Build Optimizer: applies Angular optimizations to JavaScript code
    Bundling
    Your App at the Speed of Light
    Angular Performance

    View Slide

  52. A Simple Demo App
    Dev build: 4.2 MB (without source maps)
    AoT build: 2.8 MB (without source maps)
    AoT+TreeShake: 502K
    AoT+TreeShake+BuildOptimizer: 379K (106K gzipped)
    Bundling
    Your App at the Speed of Light
    Angular Performance

    View Slide

  53. Differential Loading
    Detect the platform and only deliver files required for this platform
    First version introduced with Angular CLI 7.3.0
    core.js Polyfills required for ES5 browsers are only delivered to ES5
    browsers
    Saves another 56+ K for modern browsers
    Angular Performance
    Your App at the Speed of Light
    Bundling

    View Slide

  54. Differential Loading
    // index.html



    nomodule>




    View Slide

  55. Differential Loading
    - Differential loading support comes to all files
    - Angular CLI 8+ can produce ES5 + ES2015 bundles of your application
    - ES2015 files (smaller footprint) will be delivered to modern browsers
    only
    Angular Performance
    Your App at the Speed of Light
    Bundling

    View Slide

  56. Runtime Performance
    Bundling
    Lazy
    Loading
    Preloading
    Strategies
    Server-Side
    Rendering
    Service
    Worker
    Angular Performance
    Your App at the Speed of Light
    Agenda
    Change
    Detection
    Basics
    Zone.js &
    NgZone
    Change
    Detection
    Strategies
    Change
    Detector
    Async Pipe
    Load Time Performance

    View Slide

  57. Overview
    Angular router supports lazy loading components transparently
    Lazy loaded components are not delivered to/loaded by the client on
    boot, but on purpose
    Reduces load & perceived loading time
    Lazy Loading
    Your App at the Speed of Light
    Angular Performance

    View Slide

  58. Overview
    const ROUTES: Routes = [{
    path: 'admin',
    loadChildren: () => import('./lazy/lazy.module')
    .then(m => m.LazyModule)
    }];
    Lazy Loading
    Your App at the Speed of Light
    Angular Performance

    View Slide

  59. Runtime Performance
    Bundling
    Lazy
    Loading
    Preloading
    Strategies
    Server-Side
    Rendering
    Service
    Worker
    Angular Performance
    Your App at the Speed of Light
    Agenda
    Change
    Detection
    Basics
    Zone.js &
    NgZone
    Change
    Detection
    Strategies
    Change
    Detector
    Async Pipe
    Load Time Performance

    View Slide

  60. Configuring Lazy Loading
    NoPreloading
    - does not preload any route by
    default
    - advantage: low size footprint
    - disadvantage: takes some
    time after clicking a link to the
    lazy-loaded module, not offline
    capable
    PreloadAllModules
    - automatically preloads all
    modules after the application
    has launched (still better
    loading time!)
    - advantage: lazy-loaded
    modules now load instant (also
    on the first click), offline
    capable
    - disadvantage: higher footprint
    Angular Performance
    Your App at the Speed of Light
    Preloading Strategies

    View Slide

  61. Configuring Lazy Loading
    @NgModule({
    imports: [RouterModule.forRoot(routes, {
    preloadingStrategy: PreloadAllModules,
    })],
    exports: [RouterModule]
    })
    export class AppRoutingModule { }
    Angular Performance
    Your App at the Speed of Light
    Preloading Strategies

    View Slide

  62. Custom Strategy
    preload(route: Route, fn: () => Observable): Observable {
    // decide based on route (or other external information)
    // call fn to preload the module
    // otherwise, return of(null)
    }
    https://github.com/angular/angular/blob/8.1.x/packages/router/src/router_preloader.ts#L41
    Angular Performance
    Your App at the Speed of Light
    Preloading Strategies

    View Slide

  63. Runtime Performance
    Bundling
    Lazy
    Loading
    Preloading
    Strategies
    Server-Side
    Rendering
    Service
    Worker
    Angular Performance
    Your App at the Speed of Light
    Agenda
    Change
    Detection
    Basics
    Zone.js &
    NgZone
    Change
    Detection
    Strategies
    Change
    Detector
    Async Pipe
    Load Time Performance

    View Slide

  64. Principle
    Angular Universal
    Pre-render the website using the same sources that are served
    Once Angular kicks in, the view is replaced with the client-rendered one
    Supports Node.js (Express) & ASP.NET Core
    Server-Side Rendering
    Your App at the Speed of Light
    Angular Performance

    View Slide

  65. Principle
    Server-Side Rendering
    Component
    @Component({ … })
    class UserComponent {
    user = { name: 'Chris' };
    }
    Template
    hello {{ user.name }}
    Server Client
    Component
    @Component({ … })
    class UserComponent {
    user = { name: 'Chris' };
    }
    View Class
    var v = this.comp.user.name;
    Template
    hello {{ user.name }}
    View Class
    var v = this.comp.user.name;
    index.html

    Your App at the Speed of Light
    Angular Performance

    View Slide

  66. Purpose
    Search Engine Optimization
    Preview Links (Social Media)
    Graceful Degradation
    Reduce Perceived Loading Time/Quick First Contentful Paint (FCP)
    Improve Performance for Mobile/Low-Powered Devices
    Server-Side Rendering
    Your App at the Speed of Light
    Angular Performance

    View Slide

  67. Server
    Rendering
    Asset
    Downloads
    Client Init Client Data Paint
    The Web App Gap
    Server-Side Rendering
    Your App at the Speed of Light
    Angular Performance

    View Slide

  68. Preboot.js
    Filling the Web App Gap
    Records interactions of the user on the server-rendered part
    Replays the interaction once Angular kicks in on the client side
    Provided by the Angular team
    Open source
    https://github.com/angular/preboot
    Server-Side Rendering
    Your App at the Speed of Light
    Angular Performance

    View Slide

  69. Server
    Rendering
    Asset
    Downloads
    Client Init Client Data Paint
    Preboot.js & The Web App Gap
    Angular Performance
    Your App at the Speed of Light
    Server-Side Rendering
    Record Replay

    View Slide

  70. Runtime Performance
    Bundling
    Lazy
    Loading
    Preloading
    Strategies
    Server-Side
    Rendering
    Service
    Worker
    Angular Performance
    Your App at the Speed of Light
    Agenda
    Change
    Detection
    Basics
    Zone.js &
    NgZone
    Change
    Detection
    Strategies
    Change
    Detector
    Async Pipe
    Load Time Performance

    View Slide

  71. Idea: Never load the same resource twice
    Download resources once and store them in a local cache
    The next time the user wants to open the application, load the contents
    from there
    Makes your application sources offline-capable
    Significantly improves loading time
    Angular Performance
    Your App at the Speed of Light
    Service Worker

    View Slide

  72. Architecture
    Service Worker
    Service
    Worker
    Internet
    Website
    HTML/JS
    Cache
    fetch
    Your App at the Speed of Light
    Angular Performance

    View Slide

  73. @angular/service-worker
    Service Worker implementation provided by the Angular team
    Features
    - Caching
    - Offline Availability
    - Push Notifications
    Service Worker is generated by the CLI (prod builds only)
    ng add @angular/pwa
    Service Worker
    Your App at the Speed of Light
    Angular Performance

    View Slide

  74. 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

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

    View Slide