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

Faster Angular Applications

Faster Angular Applications

One thing is sure - performance matters! Blocking the main thread of an application, causing frame drops, is one of the most efficient ways to get rid of a significant portion of our users.

Fortunately, the day could be saved, thanks to functional programming. Coming with, at first, abstract ideas, functional programming brings the concepts of immutability and purity, which combined can dramatically improve the rendering performance of our application.

In this talk, we'll apply concepts from functional programming in the face of pure components, pure pipes, and persistent data structures. We'll demonstrate in details how we can use them to build high-performant Angular applications.

Minko Gechev

June 22, 2018
Tweet

More Decks by Minko Gechev

Other Decks in Programming

Transcript

  1. twitter.com/mgechev
    github.com/mgechev
    blog.mgechev.com
    Faster Applications
    Minko Gechev

    View Slide

  2. twitter.com/mgechev

    View Slide

  3. twitter.com/mgechev
    twitter.com/mgechev
    github.com/mgechev

    View Slide

  4. twitter.com/mgechev

    View Slide

  5. twitter.com/mgechev
    JavaScript is powerful

    View Slide

  6. twitter.com/mgechev
    Complex business applications

    View Slide

  7. twitter.com/mgechev
    Everything comes with a price tag

    View Slide

  8. twitter.com/mgechev

    View Slide

  9. twitter.com/mgechev

    View Slide

  10. twitter.com/mgechev

    View Slide

  11. twitter.com/mgechev

    View Slide

  12. twitter.com/mgechev

    View Slide

  13. View Slide

  14. View Slide

  15. twitter.com/mgechev

    View Slide

  16. twitter.com/mgechev
    • Build optimizer
    • Tree-shakable providers
    • Compression
    • Minification
    Angular (CLI) does much more!

    View Slide

  17. twitter.com/mgechev
    Why should we even care?

    View Slide

  18. twitter.com/mgechev

    View Slide

  19. twitter.com/mgechev

    View Slide

  20. twitter.com/mgechev
    https://medium.com/dev-channel/the-cost-of-javascript-84009f51e99e

    View Slide

  21. twitter.com/mgechev
    Application specific optimizations

    View Slide

  22. twitter.com/mgechev
    lazy-loading

    View Slide

  23. twitter.com/mgechev
    Lazy-loading strategies
    Route-Based
    Component-Based

    View Slide

  24. twitter.com/mgechev
    Lazy-loading strategies
    Route-Based
    Component-Based

    View Slide

  25. export const appRoutes: Routes = [{
    path: '',
    pathMatch: 'full',
    redirectTo: 'intro'
    }, {
    loadChildren: './intro/intro.module#IntroModule',
    path: 'intro'
    }, {
    loadChildren: './main/main.module#MainModule',
    path: 'main'
    }];

    View Slide

  26. twitter.com/mgechev
    Lazy-load all the things!

    View Slide

  27. twitter.com/mgechev
    • Bad UX for subsequent page views
    • Structural changes
    • Behavioral changes
    • Nested routes
    • Cross-bundle dependencies
    • Webpack’s extra bundle runtime
    Code-split gotchas

    View Slide

  28. twitter.com/mgechev
    Lazy-load all the things!??

    View Slide

  29. twitter.com/mgechev
    • Carefully pick the split points
    • Use prefetching
    Improve lazy-loading UX

    View Slide

  30. twitter.com/mgechev
    How do we pick the split points?

    View Slide

  31. twitter.com/mgechev
    often subjectively

    View Slide

  32. twitter.com/mgechev
    Data-driven bundling
    - Users behavior
    - Bundle sizes

    View Slide

  33. twitter.com/mgechev
    Data-driven bundling
    - Users behavior
    - Bundle sizes

    View Slide

  34. twitter.com/mgechev
    Step 1: Open
    https://example.com/
    Step 2: Determine JavaScript
    which is likely to be required
    Step 3: Download the
    chunks
    Step 4: Store chunks
    in browser cache
    Pre-fetching

    View Slide

  35. twitter.com/mgechev
    Prefetching Strategies

    View Slide

  36. @NgModule({
    bootstrap: [MailAppCmp],
    imports: [
    RouterModule.forRoot(ROUTES, {
    preloadingStrategy: PreloadAllModules
    })
    ]
    })
    class MailModule {}

    View Slide

  37. @NgModule({
    bootstrap: [MailAppCmp],
    imports: [
    RouterModule.forRoot(ROUTES, {
    preloadingStrategy: PreloadAllModules
    })
    ]
    })
    class MailModule {}

    View Slide

  38. twitter.com/mgechev
    Draining user’s network connection

    View Slide

  39. export const appRoutes: Routes = [{
    path: '',
    pathMatch: 'full',
    redirectTo: 'intro'
    }, {
    loadChildren: './intro/intro.module#IntroModule',
    path: 'intro',
    data: { preload: true }
    }, {
    loadChildren: './main/main.module#MainModule',
    path: 'main'
    }];

    View Slide

  40. export const appRoutes: Routes = [{
    path: '',
    pathMatch: 'full',
    redirectTo: 'intro'
    }, {
    loadChildren: './intro/intro.module#IntroModule',
    path: 'intro',
    data: { preload: true }
    }, {
    loadChildren: './main/main.module#MainModule',
    path: 'main'
    }];

    View Slide

  41. export class PreloadSelctedModulesList
    implements PreloadingStrategy {
    preload(route: Route, load: Function): Observable {
    return route.data.preload ? load() : of(null);
    }
    }

    View Slide

  42. twitter.com/mgechev
    How do we pick the priorities of the
    bundles to pre-fetch?

    View Slide

  43. twitter.com/mgechev

    View Slide

  44. twitter.com/mgechev
    What if the application changes?

    View Slide

  45. twitter.com/mgechev
    Update the app
    Collect data
    Update prefetch logic

    View Slide

  46. twitter.com/mgechev
    Update the app
    Collect data
    Update prefetch logic
    manual work

    View Slide

  47. twitter.com/mgechev
    Nested route resolution

    View Slide

  48. twitter.com/mgechev
    /a
    /a/a /a/b
    /b
    /b/a
    /
    twitter.com/mgechev

    View Slide

  49. twitter.com/mgechev
    /a
    /a/a /a/b
    /b
    /b/a
    /
    twitter.com/mgechev

    View Slide

  50. twitter.com/mgechev
    /a
    /a/a /a/b
    /b
    /b/a
    /
    twitter.com/mgechev

    View Slide

  51. twitter.com/mgechev
    /a
    /a/a /a/b
    /b
    /b/a
    /
    twitter.com/mgechev

    View Slide

  52. twitter.com/mgechev
    /a
    /a/a /a/b
    /b
    /b/a
    /
    twitter.com/mgechev

    View Slide

  53. twitter.com/mgechev
    Cluster bundles associated with
    nested routes, when it makes sense

    View Slide

  54. twitter.com/mgechev
    Cluster bundles associated with
    nested routes, when it makes sense

    View Slide

  55. twitter.com/mgechev
    Cluster bundles associated with
    nested routes, when it makes sense
    when the bundles are often requested together

    View Slide

  56. twitter.com/mgechev
    Cluster bundles associated with
    nested routes, when it makes sense
    when the bundles are often requested together
    when bundles are not too big

    View Slide

  57. twitter.com/mgechev
    • Lazy-load!
    • Lazy-load based on data
    • Overfetching matters
    • Use data-driven prefetching of assets
    • Beware of cascading route resolution
    • Use link[rel=“preload|prefetch”]
    Tips on lazy-loading

    View Slide

  58. twitter.com/mgechev
    Tooling

    View Slide

  59. twitter.com/mgechev
    • Manage the bundle sizes
    • Prefetch only the bundles likely to be used
    • Cluster small bundles in nested routes
    The tooling should

    View Slide

  60. twitter.com/mgechev
    Prefetch tooling for webpack
    webpack 4 preload-webpack-plugin
    import(
    !/* webpackPrefetch: true !*/
    '!!...');
    plugins: [
    new HtmlWebpackPlugin(),
    new PreloadWebpackPlugin()
    ]

    View Slide

  61. twitter.com/mgechev

    Prefetch bundles which might be required in future
    No manual prefetching logic
    Uses link[rel=“prefetch|preload”]

    View Slide

  62. twitter.com/mgechev


    Prefetch bundles which might be required in future
    No manual prefetching logic
    Uses link[rel=“prefetch|preload”]
    Over-fetching & bandwidth consumption
    Does not allow prioritization of bundles
    Does not work with content

    View Slide

  63. twitter.com/mgechev

    View Slide

  64. twitter.com/mgechev
    slide by @addyosmani

    View Slide

  65. twitter.com/mgechev
    $ npm run eject
    $ vim webpack.config.js

    View Slide

  66. twitter.com/mgechev
    const { GuessPlugin } =
    require('guess-webpack');
    !// !!...
    plugins: [
    !// !!...
    new GuessPlugin({
    GA: 'XXXXXXXXX'
    })
    ]
    !// !!...

    View Slide

  67. twitter.com/mgechev
    /a
    /a/a /a/b
    /b
    /b/a
    /

    View Slide

  68. twitter.com/mgechev
    Markov Chain
    / /a /a/:id /b /b/a
    / 0 0.3 0 0.7 0
    /a 0 0 0.9 0.1 0
    /a/:id 0 1 0 0 0
    /b 0 0 0 0 1
    /b/a 0 1 0 0 0
    /a
    /a/a /a/b
    /b
    /b/a
    /

    View Slide

  69. twitter.com/mgechev
    /a
    /a/a /a/b
    /b
    /b/a
    /
    Activity:
    / /a /a/:id /b /b/a
    / 0 0.3 0 0.7 0
    /a 0 0 0.9 0.1 0
    /a/:id 0 1 0 0 0
    /b 0 0 0 0 1
    /b/a 0 1 0 0 0
    Probability threshold: 0.5

    View Slide

  70. twitter.com/mgechev
    /a
    /a/a /a/b
    /b
    /b/a
    /
    Activity:
    / /a /a/:id /b /b/a
    / 0 0.3 0 0.7 0
    /a 0 0 0.9 0.1 0
    /a/:id 0 1 0 0 0
    /b 0 0 0 0 1
    /b/a 0 1 0 0 0
    Probability threshold: 0.5

    View Slide

  71. twitter.com/mgechev
    /a
    /a/a /a/b
    /b
    /b/a
    /
    Activity:
    / /a /a/:id /b /b/a
    / 0 0.3 0 0.7 0
    /a 0 0 0.9 0.1 0
    /a/:id 0 1 0 0 0
    /b 0 0 0 0 1
    /b/a 0 1 0 0 0
    Probability threshold: 0.5

    View Slide

  72. twitter.com/mgechev
    / /a /a/:id /b /b/a
    / 0 0.3 0 0.7 0
    /a 0 0 0.9 0.1 0
    /a/:id 0 1 0 0 0
    /b 0 0 0 0 1
    /b/a 0 1 0 0 0
    /a
    /a/a /a/b
    /b
    /b/a
    /
    Activity:
    - Download b.bundle.js
    Probability threshold: 0.5

    View Slide

  73. twitter.com/mgechev
    /a
    /a/a /a/b
    /b
    /b/a
    /
    Activity:
    - Download b.bundle.js
    / /a /a/:id /b /b/a
    / 0 0.3 0 0.7 0
    /a 0 0 0.9 0.1 0
    /a/:id 0 1 0 0 0
    /b 0 0 0 0 1
    /b/a 0 1 0 0 0
    Probability threshold: 0.5

    View Slide

  74. twitter.com/mgechev
    /a
    /a/a /a/b
    /b
    /b/a
    /
    Activity:
    - Download b.bundle.js
    / /a /a/:id /b /b/a
    / 0 0.3 0 0.7 0
    /a 0 0 0.9 0.1 0
    /a/:id 0 1 0 0 0
    /b 0 0 0 0 1
    /b/a 0 1 0 0 0
    Probability threshold: 0.5

    View Slide

  75. twitter.com/mgechev
    /a
    /a/a /a/b
    /b
    /b/a
    /
    / /a /a/:id /b /b/a
    / 0 0.3 0 0.7 0
    /a 0 0 0.9 0.1 0
    /a/:id 0 1 0 0 0
    /b 0 0 0 0 1
    /b/a 0 1 0 0 0
    Activity:
    - Download b.bundle.js
    - No action
    Probability threshold: 0.5

    View Slide

  76. twitter.com/mgechev
    /a
    /a/a /a/b
    /b
    /b/a
    /
    / /a /a/:id /b /b/a
    / 0 0.3 0 0.7 0
    /a 0 0 0.9 0.1 0
    /a/:id 0 1 0 0 0
    /b 0 0 0 0 1
    /b/a 0 1 0 0 0
    Activity:
    - Download b.bundle.js
    - No action
    Probability threshold: 0.5

    View Slide

  77. twitter.com/mgechev
    /a
    /a/a /a/b
    /b
    /b/a
    /
    / /a /a/:id /b /b/a
    / 0 0.3 0 0.7 0
    /a 0 0 0.9 0.1 0
    /a/:id 0 1 0 0 0
    /b 0 0 0 0 1
    /b/a 0 1 0 0 0
    Activity:
    - Download b.bundle.js
    - No action
    Probability threshold: 0.5

    View Slide

  78. twitter.com/mgechev
    /a
    /a/a /a/b
    /b
    /b/a
    /
    / /a /a/:id /b /b/a
    / 0 0.3 0 0.7 0
    /a 0 0 0.9 0.1 0
    /a/:id 0 1 0 0 0
    /b 0 0 0 0 1
    /b/a 0 1 0 0 0
    Activity:
    - Download b.bundle.js
    - No action
    - Download a.bundle.js
    Probability threshold: 0.5

    View Slide

  79. twitter.com/mgechev
    • https://mgv.io/guess
    • https://mgv.io/dd-bundling
    • https://mgv.io/cost-of-js
    • https://mgv.io/predictive-fetching
    Resources

    View Slide

  80. Runtime Performance

    View Slide

  81. twitter.com/mgechev
    Simplified
    Business application

    View Slide

  82. View Slide

  83. AppComponent
    EmployeesListComponent

    View Slide

  84. AppComponent
    EmployeesListComponent

    View Slide

  85. AppComponent
    EmployeesListComponent

    View Slide

  86. (keydown)="handleKey($event)">
    *ngFor="let item of data">
    {{ item.label }}
    {{ calculate(item.num) }}
    !

    View Slide

  87. (keydown)="handleKey($event)">
    *ngFor="let item of data">
    {{ item.label }}
    {{ calculate(item.num) }}
    !

    View Slide

  88. (keydown)="handleKey($event)">
    *ngFor="let item of data">
    {{ item.label }}
    {{ calculate(item.num) }}
    !

    View Slide

  89. (keydown)="handleKey($event)">
    *ngFor="let item of data">
    {{ item.label }}
    {{ calculate(item.num) }}
    !

    View Slide

  90. @Component(!!...)
    export class EmployeeListComponent {
    @Input() data: EmployeeData[];
    @Output() remove = new EventEmitter();
    @Output() add = new EventEmitter();
    handleKey(event: any) { !!... }
    calculate(num: number) {
    return fibonacci(num);
    }
    }

    View Slide

  91. @Component(!!...)
    export class EmployeeListComponent {
    @Input() data: EmployeeData[];
    @Output() remove = new EventEmitter();
    @Output() add = new EventEmitter();
    handleKey(event: any) { !!... }
    calculate(num: number) {
    return fibonacci(num);
    }
    }

    View Slide

  92. AppComponent
    EmployeesListComponent
    data

    View Slide

  93. @Component(!!...)
    export class EmployeeListComponent {
    @Input() data: EmployeeData[];
    @Output() remove = new EventEmitter();
    @Output() add = new EventEmitter();
    handleKey(event: any) { !!... }
    calculate(num: number) {
    return fibonacci(num);
    }
    }

    View Slide

  94. const fibonacci = n !=> {
    if (n !!=== 1 !|| n !!=== 2)
    return 1;
    return fibonacci(n - 1)
    + fibonacci(n - 2);
    };

    View Slide

  95. Slowing it down artificially

    View Slide

  96. twitter.com/mgechev
    Application Structure
    • An application component
    • Two list components
    • Slow computation for each entry

    View Slide

  97. twitter.com/mgechev
    Real data…

    View Slide

  98. View Slide

  99. 140 entries

    View Slide

  100. View Slide

  101. Why that slow?

    View Slide

  102. View Slide

  103. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    Ignored details for simplicity

    View Slide

  104. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    Ignored details for simplicity

    View Slide

  105. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    Ignored details for simplicity

    View Slide

  106. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    Ignored details for simplicity

    View Slide

  107. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    Ignored details for simplicity

    View Slide

  108. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    Ignored details for simplicity

    View Slide

  109. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    Ignored details for simplicity

    View Slide

  110. twitter.com/mgechev
    Ideas for optimization?

    View Slide

  111. twitter.com/mgechev
    OnPush
    Change Detection Strategy

    View Slide

  112. twitter.com/mgechev
    With OnPush change detection will be
    triggered when the framework, with reference
    check, determines that any of the inputs of a
    component has changed…

    View Slide

  113. twitter.com/mgechev
    Passing new reference
    triggers the change detection

    View Slide

  114. twitter.com/mgechev
    Should we copy the array
    every time?

    View Slide

  115. Why would we do that…?

    View Slide

  116. View Slide

  117. twitter.com/mgechev
    Immutable.js helps:
    • We get a new reference on change
    • We do not copy the entire data structure

    View Slide

  118. twitter.com/mgechev
    Let’s see how fast it is now!

    View Slide

  119. View Slide

  120. Non Optimized On Push
    Typing Speed

    View Slide

  121. ~3x faster but still slow…

    View Slide

  122. View Slide

  123. twitter.com/mgechev
    With OnPush change detection will be
    triggered when the framework, with reference
    check, determines that any of the inputs of a
    component has changed…or when an event
    in the component is triggered

    View Slide

  124. twitter.com/mgechev
    Let’s do some refactoring!

    View Slide

  125. AppComponent
    EmployeesListComponent
    NameInputComponent ListComponent

    View Slide

  126. AppComponent
    EmployeesListComponent
    NameInputComponent ListComponent

    View Slide

  127. AppComponent
    EmployeesListComponent
    NameInputComponent ListComponent

    View Slide

  128. View Slide

  129. twitter.com/mgechev
    Sooo much faster!

    View Slide

  130. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    ListComponenet
    (sales)
    ListComponenet
    (r&d)
    Ignored details for simplicity

    View Slide

  131. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    ListComponenet
    (sales)
    ListComponenet
    (r&d)
    Ignored details for simplicity

    View Slide

  132. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    ListComponenet
    (sales)
    ListComponenet
    (r&d)
    Ignored details for simplicity

    View Slide

  133. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    ListComponenet
    (sales)
    ListComponenet
    (r&d)
    Ignored details for simplicity

    View Slide

  134. Unoptimized Refactored with On Push
    Typing Speed

    View Slide

  135. Adding items

    View Slide

  136. twitter.com/mgechev
    Recomputing everything
    every time we add a new entry

    View Slide

  137. const fibonacci = n !=> {
    if (n !!=== 1 !|| n !!=== 2) return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
    };

    View Slide

  138. // Two properties
    // - No side effects
    // - Same result for same arguments
    const fibonacci = n !=> {
    if (n !!=== 1 !|| n !!=== 2) return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
    };

    View Slide

  139. twitter.com/mgechev
    Pure Function

    View Slide

  140. twitter.com/mgechev
    Pipes in Angular
    • Pure
    • Impure

    View Slide

  141. {{ birthday | date }}
    {{ birthday | impureDate }}

    View Slide

  142. {{ birthday | date }}
    {{ birthday | impureDate }}
    i1.ɵunv(_v, 1, 0, _ck(_v, 2, 0,
    i1.ɵnov(_v, 0), _co.birthday));
    i1.ɵunv(_v, 1, 1,
    i1.ɵnov(_v, 3).transform(_co.birthday));

    View Slide

  143. {{ birthday | date }}
    {{ birthday | impureDate }}
    i1.ɵunv(_v, 1, 0, _ck(_v, 2, 0,
    i1.ɵnov(_v, 0), _co.birthday));
    i1.ɵunv(_v, 1, 1,
    i1.ɵnov(_v, 3).transform(_co.birthday));

    View Slide

  144. twitter.com/mgechev
    Angular executes a pure pipe only when it
    detects a change to the input value. A pure
    change is either a change to a primitive input value
    (String, Number, Boolean, Symbol) or a changed
    object reference (Date, Array, Function, Object).

    View Slide

  145. @Pipe({
    name: 'calculate',
    pure: true
    })
    export class CalculatePipe {
    transform(num: number) {
    return fibonacci(num);
    }
    }

    View Slide

  146. @Component({
    changeDetection:
    ChangeDetectionStrategy.OnPush,
    template: `
    !!...
    {{ item.num | calculate }}
    !!...
    `
    })
    export class ListComponent { !!... }

    View Slide

  147. twitter.com/mgechev
    Lets benchpress it!

    View Slide

  148. View Slide

  149. Refactored with On Push Calculation in a Pure Pipe
    Adding / Removing Entries

    View Slide

  150. twitter.com/mgechev
    • Use OnPush in an isolated context
    • Consider immutable.js with OnPush
    • Use pure pipes instead of method calls
    Tips on runtime performance

    View Slide

  151. twitter.com/mgechev
    mgv.io/ng-cd - Angular’s OnPush Change Detection Strategy
    mgv.io/ng-pure - Pure Pipes and Referential Transparency
    mgv.io/ng-diff - Understanding Angular Differs
    mgv.io/ng-perf-checklist - Angular Performance Checklist
    mgv.io/ng-checklist-video - Angular Performance Checklist (video)
    Resources

    View Slide

  152. Thank you!
    twitter.com/mgechev
    github.com/mgechev
    blog.mgechev.com

    View Slide