Slide 1

Slide 1 text

Angular Performance Your App at the Speed of Light Christian Liebel @christianliebel Consultant

Slide 2

Slide 2 text

Hello, it’s me. Angular Performance Your App at the Speed of Light Christian Liebel Follow me: @christianliebel Blog: christianliebel.com Email: christian.liebel @thinktecture.com Cross-Platform & Serverless

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

In general: Reduce required computations during runtime (calculations, painting, layouting) Not covered: CSS/JS tweaks, performance metrics, … Today: Angular-specific performance topics Angular Performance Your App at the Speed of Light Runtime Performance

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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 }}!

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

A look at Angular’s dependencies "dependencies": { "@angular/common": "~7.2.0", "core-js": "^2.5.4", "rxjs": "~6.3.3", "zone.js": "~0.8.26" }, Zone.js Your App at the Speed of Light Angular Performance

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Execution Context Debugging Pending asynchronous tasks are known Profiling Measuring performance (Google Web Tracing Framework) Mocking/Testing Hooks beforeTask, … Zone.js Your App at the Speed of Light Angular Performance

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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()

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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!

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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__BLACK_LISTED_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!

Slide 29

Slide 29 text

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!

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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!

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

Overview Takes observables or promises {{ 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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

Not covered: - HTTP/2, compression, … Today: - 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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

The Problem Just-in-Time compilation (JiT) - Slow, client-side rendering - Compiler is 1.2 MB large in size - Template errors detected at runtime only - Potentially dangerous (injection attacks) Bundling Your App at the Speed of Light Angular Performance

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

ng build --prod AoT + Tree Shaking: “walks the dependency graph, top to bottom, and shakes out unused code like dead needles in a Christmas tree.” + Build Optimizer: applies Angular optimizations to JavaScript code Bundling Your App at the Speed of Light Angular Performance

Slide 52

Slide 52 text

A Simple Demo App Dev build: 4.2 MB (without source maps) AoT build: 2.8 MB (without source maps) AoT+TreeShake: 502K AoT+TreeShake+BuildOptimizer: 379K (106K gzipped) Bundling Your App at the Speed of Light Angular Performance

Slide 53

Slide 53 text

Differential Loading Detect the platform and only deliver files required for this platform First version introduced with Angular CLI 7.3.0 core.js Polyfills required for ES5 browsers are only delivered to ES5 browsers Saves another 56+ K for modern browsers Angular Performance Your App at the Speed of Light Bundling

Slide 54

Slide 54 text

Differential Loading // index.html Angular Performance Your App at the Speed of Light Bundling

Slide 55

Slide 55 text

Differential Loading - Differential loading support comes to all files - Angular CLI 8+ can produce ES5 + ES2015 bundles of your application - ES2015 files (smaller footprint) will be delivered to modern browsers only Angular Performance Your App at the Speed of Light Bundling

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

Configuring Lazy Loading @NgModule({ imports: [RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules, })], exports: [RouterModule] }) export class AppRoutingModule { } Angular Performance Your App at the Speed of Light Preloading Strategies

Slide 62

Slide 62 text

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) } https://github.com/angular/angular/blob/8.1.x/packages/router/src/router_preloader.ts#L41 Angular Performance Your App at the Speed of Light Preloading Strategies

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

Principle 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 Server-Side Rendering Your App at the Speed of Light Angular Performance

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

Purpose Search Engine Optimization Preview Links (Social Media) Graceful Degradation Reduce Perceived Loading Time/Quick First Contentful Paint (FCP) Improve Performance for Mobile/Low-Powered Devices Server-Side Rendering Your App at the Speed of Light Angular Performance

Slide 67

Slide 67 text

Server Rendering Asset Downloads Client Init Client Data Paint The Web App Gap Server-Side Rendering Your App at the Speed of Light Angular Performance

Slide 68

Slide 68 text

Preboot.js Filling the Web App Gap Records interactions of the user on the server-rendered part Replays the interaction once Angular kicks in on the client side Provided by the Angular team Open source https://github.com/angular/preboot Server-Side Rendering Your App at the Speed of Light Angular Performance

Slide 69

Slide 69 text

Server Rendering Asset Downloads Client Init Client Data Paint Preboot.js & The Web App Gap Angular Performance Your App at the Speed of Light Server-Side Rendering Record Replay

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

Architecture Service Worker Service Worker Internet Website HTML/JS Cache fetch Your App at the Speed of Light Angular Performance

Slide 73

Slide 73 text

@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) ng add @angular/pwa Service Worker Your App at the Speed of Light Angular Performance

Slide 74

Slide 74 text

Runtime Performance 1. Don’t over-optimize 2. Reduce duration of a change detection cycle - Reduce amount of bindings - Avoid binding to (computationally intensive) getters or functions 3. Reduce amount of change detection cycles - Disable zone - NgZone - Zone.js patches - ChangeDetectorRef - ChangeDetectionStrategy Angular Performance Your App at the Speed of Light Cheat Sheet

Slide 75

Slide 75 text

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