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.

82bafb0432ce4ccc9dcc26f94d5fe5bc?s=128

Minko Gechev

June 22, 2018
Tweet

Transcript

  1. 13.
  2. 14.
  3. 25.

    export const appRoutes: Routes = [{ path: '', pathMatch: 'full',

    redirectTo: 'intro' }, { loadChildren: './intro/intro.module#IntroModule', path: 'intro' }, { loadChildren: './main/main.module#MainModule', path: 'main' }];
  4. 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
  5. 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
  6. 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' }];
  7. 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' }];
  8. 41.
  9. 55.

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

    sense when the bundles are often requested together
  10. 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
  11. 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
  12. 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
  13. 60.

    twitter.com/mgechev Prefetch tooling for webpack webpack 4 preload-webpack-plugin import( !/*

    webpackPrefetch: true !*/ '!!...'); plugins: [ new HtmlWebpackPlugin(), new PreloadWebpackPlugin() ]
  14. 61.

    twitter.com/mgechev Prefetch bundles which might be required in future No

    manual prefetching logic Uses link[rel=“prefetch|preload”]
  15. 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
  16. 66.

    twitter.com/mgechev const { GuessPlugin } = require('guess-webpack'); !// !!... plugins:

    [ !// !!... new GuessPlugin({ GA: 'XXXXXXXXX' }) ] !// !!...
  17. 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 /
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 82.
  29. 90.

    @Component(!!...) export class EmployeeListComponent { @Input() data: EmployeeData[]; @Output() remove

    = new EventEmitter<EmployeeData>(); @Output() add = new EventEmitter<string>(); handleKey(event: any) { !!... } calculate(num: number) { return fibonacci(num); } }
  30. 91.

    @Component(!!...) export class EmployeeListComponent { @Input() data: EmployeeData[]; @Output() remove

    = new EventEmitter<EmployeeData>(); @Output() add = new EventEmitter<string>(); handleKey(event: any) { !!... } calculate(num: number) { return fibonacci(num); } }
  31. 93.

    @Component(!!...) export class EmployeeListComponent { @Input() data: EmployeeData[]; @Output() remove

    = new EventEmitter<EmployeeData>(); @Output() add = new EventEmitter<string>(); handleKey(event: any) { !!... } calculate(num: number) { return fibonacci(num); } }
  32. 94.

    const fibonacci = n !=> { if (n !!=== 1

    !|| n !!=== 2) return 1; return fibonacci(n - 1) + fibonacci(n - 2); };
  33. 98.
  34. 100.
  35. 102.
  36. 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…
  37. 116.
  38. 117.

    twitter.com/mgechev Immutable.js helps: • We get a new reference on

    change • We do not copy the entire data structure
  39. 119.
  40. 122.
  41. 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
  42. 128.
  43. 130.

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

    E2 En … ListComponenet (sales) ListComponenet (r&d) Ignored details for simplicity
  44. 131.

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

    E2 En … ListComponenet (sales) ListComponenet (r&d) Ignored details for simplicity
  45. 132.

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

    E2 En … ListComponenet (sales) ListComponenet (r&d) Ignored details for simplicity
  46. 133.

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

    E2 En … ListComponenet (sales) ListComponenet (r&d) Ignored details for simplicity
  47. 137.

    const fibonacci = n !=> { if (n !!=== 1

    !|| n !!=== 2) return 1; return fibonacci(n - 1) + fibonacci(n - 2); };
  48. 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); };
  49. 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));
  50. 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));
  51. 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).
  52. 145.

    @Pipe({ name: 'calculate', pure: true }) export class CalculatePipe {

    transform(num: number) { return fibonacci(num); } }
  53. 148.
  54. 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
  55. 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