Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

October 19, 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.
    Christian Liebel
    Follow me:
    @christianliebel
    Email:
    christian.liebel
    @thinktecture.com
    Cross-Platform Development &
    Serverless Cloud Architectures
    Your App at the Speed of Light
    Angular Performance

    View Slide

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

    View Slide

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

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

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

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

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

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

    View Slide

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

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

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

  13. Angular Performance
    Your App at the Speed of Light

    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": "~8.2.5",
    "rxjs": "~6.4.0",
    "tslib": "^1.10.0",
    "zone.js": "~0.9.1"
    },
    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. 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. 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

  23. Angular Performance
    Your App at the Speed of Light

    View Slide

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

  25. 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!
    DEMO

    View Slide

  26. Angular Performance
    Your App at the Speed of Light

    View Slide

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

    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__UNPATCHED_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. Angular Performance
    Your App at the Speed of Light

    View Slide

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

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

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

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

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

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

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

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

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

  43. Overview
    Takes observables, promises or synchronous values
    {{ 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

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

  45. Simplifying OnPush
    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
    Async Pipe

    View Slide

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

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

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

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

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

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

  52. Tree Shaking
    “A Tree Shaker walks the dependency graph, top to bottom, and shakes
    out unused code like dead needles in a Christmas tree.”
    Bundling
    Your App at the Speed of Light
    Angular Performance

    View Slide

  53. !function(){
    var obj = {
    foo: function () { // Hi there! },
    bar: function () { this.baz(); },
    baz: function () { // stub }
    };
    obj.foo();
    }();
    Tree Shaking Principle
    Bundling
    Your App at the Speed of Light
    Angular Performance

    View Slide

  54. Differential Loading
    ES5: function add(a,b){return a+b}
    ES 2015: add=(a,b)=>a+b
    ES 2015 introduced shorter syntax (smaller footprint/saves bandwidth)
    Detect the platform and only deliver files required for this platform
    Larger ES5 bundles are only delivered to legacy browsers
    Angular Performance
    Your App at the Speed of Light
    Bundling

    View Slide

  55. Differential Loading
    // index.html


    type="module">
    defer>

    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. Architecture
    Lazy Loading is module-based
    Module is resolved via a dynamic import:
    import('./feature.module').then(m => m.FeatureModule)
    Routing is delegated to the feature module called
    Lazy Loading
    Your App at the Speed of Light
    Angular Performance

    View Slide

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

    View Slide

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

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

  62. Configuration
    @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

  63. Custom Strategy
    It’s also possible to provide a custom strategy
    - when modules belong to each other
    - it’s expected that the user will access a certain module next (e.g.
    checkout after cart module)
    Developer can decide which modules should be preloaded on a
    navigation event
    Developer gets access to the modules which have not been preloaded
    yet
    Angular Performance
    Your App at the Speed of Light
    Preloading Strategies

    View Slide

  64. 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)
    }
    Angular Performance
    Your App at the Speed of Light
    Preloading Strategies

    View Slide

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

  66. 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, Hapi.js & plain sockets
    JiT and AoT support (AoT strictly recommended for production)
    Server-Side Rendering
    Your App at the Speed of Light
    Angular Performance

    View Slide

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

  68. ng add @nguniversal/express-engine --clientProject perf-demo
    npm run build:ssr
    npm run serve:ssr
    Angular Performance
    Your App at the Speed of Light
    Server-Side Rendering

    View Slide

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

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

  71. Key Technology
    Service Worker
    Service
    Worker
    Internet
    Website
    HTML/JS
    Cache
    fetch
    Your App at the Speed of Light
    Angular Performance

    View Slide

  72. @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)
    Service Worker
    Your App at the Speed of Light
    Angular Performance

    View Slide

  73. Features
    ng add @angular/pwa
    ng build --prod
    cd dist/browser
    npx lite-server
    Service Worker
    Your App at the Speed of Light
    Angular Performance

    View Slide

  74. https://pwapraxis.liebel.io
    Service Worker
    Your App at the Speed of Light
    Angular Performance
    DEMO

    View Slide

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

    View Slide