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

Angular-Performance: So zünden Sie den Turbo

Angular-Performance: So zünden Sie den Turbo

Was in zwei Sekunden nicht lädt, wird nie wieder aufgerufen: Die Erwartung von Anwendern an die Performance von Webanwendungen nimmt stetig zu. Nach dem Laden einer Webanwendung muss sich diese schnell und flüssig anfühlen, damit der Anwender sein Ziel erreichen kann. In unserem Thinktecture-Webinar zeigt Ihnen Chrsitian Liebel mit Zone.js, Change Detection und Service Workern die wichtigsten Performance-Stellschrauben des Angular-Frameworks, damit Sie auch für Ihre Angular-App den Turbo zünden können.

Christian Liebel

December 02, 2020
Tweet

More Decks by Christian Liebel

Other Decks in Programming

Transcript

  1. Angular Performance: So zünden Sie den Turbo Thinktecture Webinar Agenda

    Change Detection & NgZone Change Detection Strategies Lazy Loading Service Workers
  2. Angular Performance: So zünden Sie den Turbo Thinktecture Webinar Agenda

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

    layouting - detecting changes Angular Performance: So zünden Sie den Turbo Thinktecture Webinar Runtime Performance
  4. Hi Angular! Basics // app.component.html <h1>Hi {{ title }}!</h1> //

    app.component.ts @Component({ /* … */ }) export class AppComponent { title = 'Angular'; } Angular Performance: So zünden Sie den Turbo Thinktecture Webinar Change Detection
  5. 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: So zünden Sie den Turbo Thinktecture Webinar Change Detection Hi Foo! Hi Angular! Update
  6. Change Detector Tree AppComponent NavComponent ContentComponent ListComponent Angular Performance: So

    zünden Sie den Turbo Thinktecture Webinar Change Detection AppComponent CD NavComponent CD ContentComponent CD ListComponent CD CHANGE
  7. 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: So zünden Sie den Turbo Thinktecture Webinar Change Detection DEMO
  8. 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: So zünden Sie den Turbo Thinktecture Webinar Change Detection
  9. 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: So zünden Sie den Turbo Thinktecture Webinar Change Detection DEMO
  10. How to detect a change? AppComponent NavComponent ContentComponent ListComponent Angular

    Performance: So zünden Sie den Turbo Thinktecture Webinar Zone.js AppComponent CD NavComponent CD ContentComponent CD ListComponent CD CHANGE
  11. A look at Angular’s dependencies "dependencies": { "@angular/common": "~11.0.1", "rxjs":

    "~6.6.0", "tslib": "^2.0.0", "zone.js": "~0.10.2" }, Zone.js Thinktecture Webinar Angular Performance: So zünden Sie den Turbo
  12. 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 Thinktecture Webinar Angular Performance: So zünden Sie den Turbo
  13. 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); }; Thinktecture Webinar Angular Performance: So zünden Sie den Turbo
  14. A Meta-Monkey Patch Zone.js setTimeout setInterval geolocation.getCurrentPosition XMLHttpRequest PromiseRejectionEvent requestAnimationFrame

    click focus mousemove addEventListener Thinktecture Webinar Angular Performance: So zünden Sie den Turbo
  15. NgZone Angular Performance: So zünden Sie den Turbo Thinktecture Webinar

    Zone.js current (global) zone NgZone Angular boot
  16. 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: So zünden Sie den Turbo Thinktecture Webinar Zone.js NgZone setTimeout setInterval onclick Detect changes Detect changes Detect changes
  17. Change Detection Trigger https://github.com/angular/angular/blob/master/packages/core/src/application_ref.ts Angular Performance: So zünden Sie den

    Turbo Thinktecture Webinar Zone.js NgZone.onMicrotaskEmpty ApplicationRef.tick() view1.detectChanges() view2.detectChanges() viewN.detectChanges()
  18. Common Pitfalls Long CD cycles in combination with high-frequency events

    - mousemove - scroll - requestAnimationFrame - setInterval with short intervals (clocks!) Angular Performance: So zünden Sie den Turbo Thinktecture Webinar Zone.js DEMO
  19. NgZone Zone.js current (global) zone NgZone rAF Detect changes rAF

    Detect changes rAF Detect changes rAF Detect changes Thinktecture Webinar Angular Performance: So zünden Sie den Turbo
  20. 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 Thinktecture Webinar Angular Performance: So zünden Sie den Turbo ! View and model can get out of sync! DEMO
  21. NgZone Zone.js current (global) zone NgZone rAF rAF rAF rAF

    Thinktecture Webinar Angular Performance: So zünden Sie den Turbo
  22. 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: So zünden Sie den Turbo Thinktecture Webinar Zone.js ! View and model can get out of sync!
  23. Event Coalescing <div (click)="doSomething()"> <button (click)="doSomethingElse()">Test</button> </div> // main.ts platformBrowserDynamic().bootstrapModule(AppModule,

    { ngZoneEventCoalescing: true }); Angular Performance: So zünden Sie den Turbo Thinktecture Webinar Zone.js ! App behavior may change! CHANGE CHANGE Coalescing
  24. Disable Zone (= disable async change detection!) platformBrowserDynamic().bootstrapModule(AppModule, { ngZone:

    'noop' }); constructor(applicationRef: ApplicationRef) { applicationRef.tick(); // trigger CD yourself } Angular Performance: So zünden Sie den Turbo Thinktecture Webinar Zone.js ! View and model can get out of sync!
  25. Angular Performance: So zünden Sie den Turbo Thinktecture Webinar Agenda

    Change Detection & NgZone Change Detection Strategies Lazy Loading Service Workers
  26. Overview Default Uses Zone.js for detecting changes and updates bindings

    OnPush Restricts change detection to changes of @Input parameters Angular Performance: So zünden Sie den Turbo Thinktecture Webinar Change Detection Strategies AppComponent CD NavComponent CD ContentComponent CD ListComponent CD AppComponent CD NavComponent CD ContentComponent CD ListComponent CD OnPush
  27. 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 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: So zünden Sie den Turbo Thinktecture Webinar Change Detection Strategies ! View and model can get out of sync!
  28. // app.component.ts @Component({ changeDetection: ChangeDetectionStrategy.OnPush }) /* … */ ngOnInit()

    { requestAnimationFrame(() => this.ngOnInit()); // this._ngZone.runOutsideAngular(() => request…); } Angular Performance: So zünden Sie den Turbo Thinktecture Webinar Change Detection Strategies DEMO
  29. 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: So zünden Sie den Turbo Thinktecture Webinar Change Detector
  30. markForCheck() Explicitly marks a component as dirty/changed (when using OnPush)

    Angular Performance: So zünden Sie den Turbo Thinktecture Webinar Change Detector AppComponent CD NavComponent CD ContentComponent CD ListComponent CD DIRTY AppComponent CD NavComponent CD ContentComponent CD ListComponent CD OnPush
  31. markForCheck() constructor(private dataService: DataService, private cdRef: ChangeDetectorRef) {} ngOnInit() {

    this.dataService.updates$.subscribe(newData => { this.data = newData; this.cdRef.markForCheck(); }); } Angular Performance: So zünden Sie den Turbo Thinktecture Webinar Change Detector
  32. 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: So zünden Sie den Turbo Thinktecture Webinar Async Pipe
  33. 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: So zünden Sie den Turbo Thinktecture Webinar Async Pipe
  34. Simplifying OnPush constructor(private dataService: DataService, private cdRef: ChangeDetectorRef) {} ngOnInit()

    { this.dataService.updates$.subscribe(newData => { this.data = newData; this.cdRef.markForCheck(); }); } Angular Performance: So zünden Sie den Turbo Thinktecture Webinar Async Pipe
  35. Simplifying OnPush // component.ts data$: Observable<string>; constructor(dataService: DataService) { this.data$

    = this.dataService.updates$; } // component.html {{ data$ | async }} Angular Performance: So zünden Sie den Turbo Thinktecture Webinar Async Pipe DEMO
  36. Detaching Components changeDetector.detach(); changeDetector.reattach(); Angular Performance: So zünden Sie den

    Turbo Thinktecture Webinar 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!
  37. 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: So zünden Sie den Turbo Thinktecture Webinar Change Detector
  38. - Reduce initial load (size & computation) - Reduce perceived

    loading time - Prevent downloading the same resource again Angular Performance: So zünden Sie den Turbo Thinktecture Webinar Load Time Performance
  39. Angular Performance: So zünden Sie den Turbo Thinktecture Webinar Agenda

    Change Detection & NgZone Change Detection Strategies Lazy Loading Service Workers
  40. 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 Thinktecture Webinar Angular Performance: So zünden Sie den Turbo
  41. 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 Thinktecture Webinar Angular Performance: So zünden Sie den Turbo
  42. Overview const ROUTES: Routes = [{ path: 'admin', loadChildren: ()

    => import('app/admin/admin.module') .then(a => a.AdminModule) }]; Lazy Loading Thinktecture Webinar Angular Performance: So zünden Sie den Turbo DEMO
  43. 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: So zünden Sie den Turbo Thinktecture Webinar Preloading Strategies
  44. Configuring Lazy Loading @NgModule({ imports: [RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules, })],

    exports: [RouterModule] }) export class AppRoutingModule { } Angular Performance: So zünden Sie den Turbo Thinktecture Webinar Preloading Strategies
  45. 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: So zünden Sie den Turbo Thinktecture Webinar 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: So zünden Sie den Turbo Thinktecture Webinar Lazy Loading
  47. Angular Performance: So zünden Sie den Turbo Thinktecture Webinar 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: So zünden Sie den Turbo Thinktecture Webinar Service Worker
  49. Key Technology Service Worker Service Worker Internet Website HTML/JS Cache

    fetch Thinktecture Webinar Angular Performance: So zünden Sie den Turbo
  50. Commands ng add @angular/pwa ng build --prod cd dist/perf-demo npx

    lite-server Service Worker Thinktecture Webinar Angular Performance: So zünden Sie den Turbo 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: So zünden Sie den Turbo Thinktecture Webinar Load-Time Performance