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

What doesn't load in two seconds will never be accessed again: User expectations for web application performance are steadily increasing. After a web application loads, it needs to feel fast and fluid in order for the user to achieve their goal. In our Thinktecture webinar, Christian Liebel will show you Angular’s most important optimization options (Zone.js, Change Detection and Service Workers), so that you can ignite the turbo for your Angular app!

12c88a3a10478fa18d0363b3ba3d9df1?s=128

Christian Liebel
PRO

March 11, 2021
Tweet

Transcript

  1. Angular Performance Your App at the Speed of Light Christian

    Liebel @christianliebel Consultant
  2. Angular Performance Your App at the Speed of Light Agenda

    Change Detection & NgZone Change Detection Strategies Lazy Loading Service Workers
  3. Angular Performance Your App at the Speed of Light Agenda

    Change Detection & NgZone Change Detection Strategies Lazy Loading Service Workers
  4. Reduce required computations during runtime - calculations - painting -

    layouting - detecting changes Angular Performance Your App at the Speed of Light Runtime Performance
  5. Hi Angular! Basics // app.component.html <h1>Hi {{ title }}!</h1> //

    app.component.ts @Component({ /* … */ }) export class AppComponent { title = 'Angular'; } Angular Performance Your App at the Speed of Light Change Detection
  6. Basics // app.component.html <h1>Hi {{ title }}!</h1> <button (click)="update()"> Update

    </button> // 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
  7. Component Tree AppComponent NavComponent ContentComponent ListComponent Angular Performance Your App

    at the Speed of Light Change Detection
  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
  9. 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
  10. 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
  11. 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
  12. 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
  13. A look at Angular’s dependencies "dependencies": { "@angular/common": "~11.2.0", "rxjs":

    "~6.6.0", "tslib": "^2.0.0", "zone.js": "~0.10.2" }, Zone.js Your App at the Speed of Light Angular Performance
  14. Overview Provided by the Angular team Open-source https://github.com/angular/zone.js 1. Provides

    an execution context for asynchronous JavaScript 2. A meta-monkey patch Zone.js Your App at the Speed of Light Angular Performance
  15. Execution Context Oversimplified Zone.run(main); onZoneEnter(); function main() { a(); setTimeout(b,

    0); c(); } onZoneLeave(); Zone.js const orig = window.setTimeout; window.setTimeout = (c, t) => { orig(() => { onZoneEnter(); c(); onZoneLeave(); }, t); }; Your App at the Speed of Light Angular Performance
  16. 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
  17. NgZone Angular Performance Your App at the Speed of Light

    Zone.js current (global) zone NgZone Angular boot
  18. 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
  19. 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()
  20. 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
  21. NgZone Zone.js current (global) zone NgZone rAF Detect changes rAF

    Detect changes rAF Detect changes rAF Detect changes Your App at the Speed of Light Angular Performance
  22. 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
  23. NgZone Zone.js current (global) zone NgZone rAF rAF rAF rAF

    Your App at the Speed of Light Angular Performance
  24. 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!
  25. Event Coalescing <div (click)="doSomething()"> <button (click)="doSomethingElse()">Test</button> </div> // main.ts platformBrowserDynamic().bootstrapModule(AppModule,

    { ngZoneEventCoalescing: true }); Angular Performance Your App at the Speed of Light Zone.js ! App behavior may change! CHANGE CHANGE Coalescing
  26. 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!
  27. Angular Performance Your App at the Speed of Light Agenda

    Change Detection & NgZone Change Detection Strategies Lazy Loading Service Workers
  28. 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
  29. OnPush <my-component [foo]="bar"> </my-component> @Component({ selector: 'my-component', template: '{{ foo

    }}', changeDetection: ChangeDetectionStrategy.OnPush }) export class MyComponent { @Input() public foo: string; } Change detection only reacts to events within the component and changes of @Input() parameters Angular compares the values passed to an @Input() via Object.is(oldValue, newValue) 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!
  30. // app.component.ts @Component({ changeDetection: ChangeDetectionStrategy.OnPush }) /* … */ ngOnInit()

    { requestAnimationFrame(() => this.ngOnInit()); // this._ngZone.runOutsideAngular(() => request…); } Angular Performance Your App at the Speed of Light Change Detection Strategies DEMO
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. Simplifying OnPush // component.ts data$: Observable<string>; constructor(dataService: DataService) { this.data$

    = this.dataService.updates$; } // component.html {{ data$ | async }} Angular Performance Your App at the Speed of Light Async Pipe DEMO
  38. 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!
  39. Findings Reduce amount of change detection cycles - Disable Zone.js

    (not a good idea in most cases) - Coalesce CD cycles (might change behavior of existing apps) - 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
  40. - 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
  41. Angular Performance Your App at the Speed of Light Agenda

    Change Detection & NgZone Change Detection Strategies Lazy Loading Service Workers
  42. 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
  43. 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
  44. 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 DEMO
  45. 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
  46. Unrelated To Routes Angular CLI generates bundles for all import()s

    and transparently downloads them when the function is invoked. function onClick() { import('./heavy-bundle').then(bundle => bundle.compute()); } Angular Performance Your App at the Speed of Light Lazy Loading
  47. Angular Performance Your App at the Speed of Light Agenda

    Change Detection & NgZone Change Detection Strategies Lazy Loading Service Workers
  48. 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
  49. Key Technology Service Worker Service Worker Internet Website HTML/JS Cache

    fetch Your App at the Speed of Light Angular Performance
  50. Commands ng add @angular/pwa ng build --prod cd dist/perf-demo npx

    lite-server Service Worker Your App at the Speed of Light Angular Performance DEMO
  51. Findings – Prevent downloading the same resource twice by using

    the Service Worker – Postpone loading less frequently used parts of your application by leveraging lazy loading Angular Performance Your App at the Speed of Light Load-Time Performance
  52. Thank you for your kind attention! Christian Liebel @christianliebel christian.liebel@thinktecture.com