Christian Liebel
@christianliebel
Consultant
Angular-Performance:
So zünden Sie den Turbo
Thinktecture Webinar
Slide 2
Slide 2 text
Angular Performance: So zünden Sie den Turbo
Thinktecture Webinar
Agenda
Change
Detection &
NgZone
Change
Detection
Strategies
Lazy
Loading
Service
Workers
Slide 3
Slide 3 text
Angular Performance: So zünden Sie den Turbo
Thinktecture Webinar
Agenda
Change
Detection &
NgZone
Change
Detection
Strategies
Lazy
Loading
Service
Workers
Slide 4
Slide 4 text
Reduce required computations during runtime
- calculations
- painting
- layouting
- detecting changes
Angular Performance: So zünden Sie den Turbo
Thinktecture Webinar
Runtime Performance
Slide 5
Slide 5 text
Hi Angular!
Basics
// app.component.html
Hi {{ title }}!
// app.component.ts
@Component({ /* … */ })
export class AppComponent {
title = 'Angular';
}
Angular Performance: So zünden Sie den Turbo
Thinktecture Webinar
Change Detection
Slide 6
Slide 6 text
Basics
// app.component.html
Hi {{ title }}!
Update
// 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
Slide 7
Slide 7 text
Component Tree
AppComponent
NavComponent ContentComponent
ListComponent
Angular Performance: So zünden Sie den Turbo
Thinktecture Webinar
Change Detection
Slide 8
Slide 8 text
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
Slide 9
Slide 9 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: So zünden Sie den Turbo
Thinktecture Webinar
Change Detection
DEMO
Slide 10
Slide 10 text
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
Slide 11
Slide 11 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: So zünden Sie den Turbo
Thinktecture Webinar
Change Detection
DEMO
Slide 12
Slide 12 text
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
Slide 13
Slide 13 text
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
Slide 14
Slide 14 text
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
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
Slide 17
Slide 17 text
NgZone
Angular Performance: So zünden Sie den Turbo
Thinktecture Webinar
Zone.js
current (global) zone
NgZone
Angular boot
Slide 18
Slide 18 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: So zünden Sie den Turbo
Thinktecture Webinar
Zone.js
NgZone
setTimeout
setInterval
onclick
Detect changes
Detect changes
Detect changes
Slide 19
Slide 19 text
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()
Slide 20
Slide 20 text
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
Slide 21
Slide 21 text
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
Slide 22
Slide 22 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
Thinktecture Webinar
Angular Performance: So zünden Sie den Turbo
! View and model can
get out of sync!
DEMO
Slide 23
Slide 23 text
NgZone
Zone.js
current (global) zone
NgZone
rAF
rAF
rAF
rAF
Thinktecture Webinar
Angular Performance: So zünden Sie den Turbo
Slide 24
Slide 24 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__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!
Slide 25
Slide 25 text
Event Coalescing
Test
// 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
Slide 26
Slide 26 text
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!
Slide 27
Slide 27 text
Angular Performance: So zünden Sie den Turbo
Thinktecture Webinar
Agenda
Change
Detection &
NgZone
Change
Detection
Strategies
Lazy
Loading
Service
Workers
Slide 28
Slide 28 text
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
Slide 29
Slide 29 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: So zünden Sie den Turbo
Thinktecture Webinar
Change Detection Strategies
! View and model can
get out of sync!
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
Slide 32
Slide 32 text
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
Slide 33
Slide 33 text
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
Slide 34
Slide 34 text
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
Slide 35
Slide 35 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: So zünden Sie den Turbo
Thinktecture Webinar
Async Pipe
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!
Slide 39
Slide 39 text
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
Slide 40
Slide 40 text
- 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
Slide 41
Slide 41 text
Angular Performance: So zünden Sie den Turbo
Thinktecture Webinar
Agenda
Change
Detection &
NgZone
Change
Detection
Strategies
Lazy
Loading
Service
Workers
Slide 42
Slide 42 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
Thinktecture Webinar
Angular Performance: So zünden Sie den Turbo
Slide 43
Slide 43 text
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
Slide 44
Slide 44 text
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
Slide 45
Slide 45 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: So zünden Sie den Turbo
Thinktecture Webinar
Preloading Strategies
Slide 46
Slide 46 text
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
Slide 47
Slide 47 text
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
Slide 48
Slide 48 text
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
Slide 49
Slide 49 text
Angular Performance: So zünden Sie den Turbo
Thinktecture Webinar
Agenda
Change
Detection &
NgZone
Change
Detection
Strategies
Lazy
Loading
Service
Workers
Slide 50
Slide 50 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: So zünden Sie den Turbo
Thinktecture Webinar
Service Worker
Slide 51
Slide 51 text
Key Technology
Service Worker
Service
Worker
Internet
Website
HTML/JS
Cache
fetch
Thinktecture Webinar
Angular Performance: So zünden Sie den Turbo
Slide 52
Slide 52 text
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
Slide 53
Slide 53 text
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
Slide 54
Slide 54 text
Thank you
for your kind attention!
Christian Liebel
@christianliebel
[email protected]