Slide 1

Slide 1 text

AngularArchitects.io | @ManfredSteyer Alexander Thalhammer | @LX_T Angular Runtime Performance Optimization https://github.com/L-X-T/ng-days-2024

Slide 2

Slide 2 text

About me …

Slide 3

Slide 3 text

My office ☺

Slide 4

Slide 4 text

Hi, it's me → @LX_T • Alex Thalhammer from Graz, Austria (since 1983) • Angular Software Tree GmbH (since 2019) • WebDev for 22+ years (I‘ve come a long way baby) • WordPress Dev (Web, PHP & jQuery, 2011 - 2017) • Angular Dev (Web, TS, Rx since 2017 - NG 4.0.0 ☺) • Angular Evangelist, Coach & Consultant (since 2020) • Member of Angular Architects https://www.angulararchitects.io/ https://alex.thalhammer.name/

Slide 5

Slide 5 text

Pollings • OS • Mobile OS? • Browser • IntelliJ/WebStorm vs VS Code

Slide 6

Slide 6 text

Pollings • Which framework is the fastest? • Do you prefer demos or labs?

Slide 7

Slide 7 text

About this workshop …

Slide 8

Slide 8 text

My Goals You should • understand the importance of good performance • know how to measure the performance • know how to tweak the performance • become a performance evangelist in her/his project/team/company

Slide 9

Slide 9 text

We work together • Don’t hesitate to interupt me at ANY TIME • questions • remarks or • any other feedback ☺ • Somebody else might have the same question • Be a hero and ask it • Seriously, it makes the workshop better for all of us ☺ • After the workshop please also share feedback ☺

Slide 10

Slide 10 text

Timetable (approx.) • 09:00 – 10:30 • 1/2h break • 11:00 – 12:30 • 1h break • 13:30 Initial Load Optimization ☺

Slide 11

Slide 11 text

Agenda Change Detection Change Detection NG 17 syntax @for track Virt. Scrolling, UX, RxJS

Slide 12

Slide 12 text

What is Performance? • What did you say? • Web performance refers to the speed in which web pages are downloaded and displayed on the user's web browser. Web performance optimization (WPO), or website optimization is the field of knowledge about increasing web performance. -- https://en.wikipedia.org/wiki/Web_performance

Slide 13

Slide 13 text

Why Performance Optimization?

Slide 14

Slide 14 text

Angular Performance Optimization WTF?

Slide 15

Slide 15 text

Angular Performance Optimization We distinguish between • Initial load performance (classical web performance) • Runtime performance (during usage, e.g. scrolling frame rate)

Slide 16

Slide 16 text

Starter Kit • Incl. Slides • And Labs (exercises) • Clone from https://github.com/L-X-T/ng-days-perf-2024

Slide 17

Slide 17 text

Ready for Takeoff! • What are your questions?

Slide 18

Slide 18 text

AngularArchitects.io | @ManfredSteyer Alexander Thalhammer | @LX_T Runtime Performance Change Detection

Slide 19

Slide 19 text

Outline - Runtime Performance • Change Detection • Further Best Practices

Slide 20

Slide 20 text

Outline - Change Detection • Out of bound change detection • Zone pollution by 3rd party libs • Optimization with state or flags • Optimization with Angular Pipes

Slide 21

Slide 21 text

Out of bound change detection • Problem: Local state change triggers change detection in other comps E.g. Input field keydown triggers CD in other components • Identify: • Use the infamous blink() or • Angular DevTools Profiler • console.log() in change detection lifecycle hook (e.g. ngDoCheck) • Solution: ChangeDetectionStrategy.OnPush as default

Slide 22

Slide 22 text

Change Detection in Angular

Slide 23

Slide 23 text

Demo Change Detection

Slide 24

Slide 24 text

Change Detection

Slide 25

Slide 25 text

DOM Rendering Model Template DOM

Slide 26

Slide 26 text

Change Detection • 1.) User or App changes the model (e.g. input, blur or click) • 2.) NG CD checks for every component (from root to leaves) if the corresponding component model has changes and thus its view (DOM) needs to be updated • 3.) If yes then update / rerender the component’s view (DOM)

Slide 27

Slide 27 text

Change Detection – From Root To Leaves Img src: https://mokkapps.de/blog/the-last-guide-for-angular-change-detection-you-will-ever-need/

Slide 28

Slide 28 text

Change Detection – Rerender Components before rerender after rerender content after rerender view

Slide 29

Slide 29 text

Demo Default Strategy & Blink

Slide 30

Slide 30 text

Digression – what triggers CD? • browser events (click, focus, blur, keyup, etc.) • XMLHttpRequests (AJAX / HttpClient) • setTimeout() and setInterval() • often used as a "hack" to trigger CD • "I have no clue how this works" • Websockets

Slide 31

Slide 31 text

Performance- Tuning with OnPush

Slide 32

Slide 32 text

Change Detection – OnPush Strategy Angular just checks when “notified” Img src: https://mokkapps.de/blog/the-last-guide-for-angular-change-detection-you-will-ever-need/

Slide 33

Slide 33 text

Activate OnPush Strategy @Component({ […] changeDetection: ChangeDetectionStrategy.OnPush }) export class FlightCardComponent { […] @Input({ required: true }) flight!: Flight; }

Slide 34

Slide 34 text

Demo OnPush Strategy

Slide 35

Slide 35 text

Change Detection

Slide 36

Slide 36 text

"Notify" about change? • 1 Change bound data (@Input) • OnPush: Angular just compares the object reference! • e. g. oldFlight !== newFlight (BTW: like ngOnChanges) • 2 Raise event within the component and its children (e.g. @Output) • 3 Emit in a bound observable into the async pipe | or update a signal • {{ flights$ | async }} | {{ flightsSignal() }} • 4 Do it manually (cdr.markForCheck()) • Don't do this at home ;-) • But there are reasonable cases (where we can neither use 1 nor 3)

Slide 37

Slide 37 text

CDR - markForCheck() vs detectChanges() • Use CDR.markForCheck() to notify the next CD cycle if using OnPush • Useful when you're bypassing the ChangeDetectionStrategy.OnPush e.g. by mutating some data or you’ve just updated the components model • Use CDR.detectChanges() to trigger CD immediately for this view and it’s children respecting the its/their CD strategy • Useful when you've updated the model after angular has run it's change detection, or if the update hasn't been in Angular world at all • For the whole app (from root) you can do ApplicationRef.tick()

Slide 38

Slide 38 text

Set OnPush as default • Add to angular.json / project.json schematic "@schematics/angular:component": { "style": "scss", "changeDetection": "OnPush" } • Add an ESlint rule "@angular-eslint/prefer-on-push-component-change-detection": "warn" • OnPush in every component? • well yes, but • optional in smart components (and root)

Slide 39

Slide 39 text

Thought experiment • What if would handle use case logic? • e.g. communicate with API • Number of requests ==> Performance? • Traceability? • Reusability? Page ▪ 39

Slide 40

Slide 40 text

Smart vs. Dumb Components Smart / Controller •1 per feature / use case / route •Business logic •Container Dumb / Presentational •Independent of Use Case •Reusable •Often Leafs Page ▪ 40

Slide 41

Slide 41 text

Zone pollution by 3rd party libs (charts)

Slide 42

Slide 42 text

Demo Zone Pollution

Slide 43

Slide 43 text

Zone pollution by 3rd party libs (charts) • Problem: Callbacks that trigger redundant change detection cycles • Identify: Use the infamous blink() or the Angular DevTools Profiler • E.g. MouseEvent listeners • requestAnimationFrame() or • setTimeout() • Solution: Run outside of NG Zone • Inject (private ngZone: NgZone) • Call this.ngZone.runOutsideAngular(() => doStuff) • https://angular.io/guide/change-detection-zone-pollution • Alternative: Using cdr.detach() for components

Slide 44

Slide 44 text

Demo Fixed Zone Pollution

Slide 45

Slide 45 text

ChangeDetectorRef API, once more Img src: https://www.telerik.com/blogs/simplifying-angular-change-detection/

Slide 46

Slide 46 text

Lab 03a Runtime Performance – Change Detection

Slide 47

Slide 47 text

Optimization with state or flags • Problem: Redundant calculations for conditions • Identify: Methods being executed in *ngIf / @if statements • Solution: Use (simple) State Management (e.g. Subjects or use boolean flags) or strings, that only change when they should

Slide 48

Slide 48 text

Derail – what's the purpose of? Services Directives Pipes?

Slide 49

Slide 49 text

Optimization with Angular Pipes • Problem: Redundant calculations / transforming / formatting • Identify: Methods being executed in string interpolations in the template or similar things slowing change detection cycles • Solution: Use (pure) Angular Pipes

Slide 50

Slide 50 text

Demo Pipes

Slide 51

Slide 51 text

Recap • Out of bound change detection • Zone pollution by 3rd party libs • Optimization with state or flags • Optimization with Angular Pipes

Slide 52

Slide 52 text

Questions?

Slide 53

Slide 53 text

References • Minko Gechev (@mgechev) for Angular on YouTube • https://www.youtube.com/watch?v=FjyX_hkscII • https://www.youtube.com/watch?v=f8sA-i6gkGQ • New in NG 17, 18+: https://www.youtube.com/watch?v=2M17gRQbgfI • Resolving Zone Pollution • https://angular.io/guide/change-detection-zone-pollution • Angular Performance Optimization using Pure Pipe • https://www.youtube.com/watch?v=YsOf90RZfss

Slide 54

Slide 54 text

AngularArchitects.io | @ManfredSteyer Alexander Thalhammer | @LX_T Runtime Performance Further Best Practices

Slide 55

Slide 55 text

Outline - Runtime Performance • Change Detection • Further Best Practices

Slide 56

Slide 56 text

Outline • Use track in @for if possible • Avoid large component trees • Use Spinners and preview thumbs • Optimistic updates • Bonus: RxJS Subscription Management

Slide 57

Slide 57 text

Handling large ng for loops

Slide 58

Slide 58 text

Using trackBy in *ngFor • Problem: Angular replaces all items in *ngFor upon changes • Identify: Easy - search for "*ngFor" • Solution: Use the trackBy function

Slide 59

Slide 59 text

Performance benchmark https://krausest.github.io/js-framework-benchmark/current.html

Slide 60

Slide 60 text

Using track in @for • Automatically required @for (flight of flights; track flight.id) { [...] } @empty { No flights found. }

Slide 61

Slide 61 text

Demo for track

Slide 62

Slide 62 text

Avoid large component trees • Problem: Too many (100+) components are loaded • Identify: Lots of components slowing down frame rate • Solution: On demand component rendering • E.g. Pagination or Angular CDKs

Slide 63

Slide 63 text

Demo Virtual Scrolling

Slide 64

Slide 64 text

Other UX improvements

Slide 65

Slide 65 text

Spinners & Preview Thumbs Twitter / Insta / …

Slide 66

Slide 66 text

Use Spinners and preview thumbs • Problem: App waits for backend before showing content • Identify: Waiting for API data to show a view (page) • Solution: Show view (page) immediately • Show spinners to indicate data is still loading • Even more sophisticated: show preview images (used everywhere on big platforms!)

Slide 67

Slide 67 text

Optimistic Updates E.g. Like Buttons

Slide 68

Slide 68 text

Optimistic Updates • Problem: App waits for backend for confirmations • Identify: Spinner showing when clicking on save • Solution: Confirm action immediately • Go back in case of an error (e.g. no network)

Slide 69

Slide 69 text

Why asynchronicity? Asynchronous operations (API requests) Interactive behavior (user input) Websockets Server Send Events (Push)

Slide 70

Slide 70 text

Which solution? RxJS Signals

Slide 71

Slide 71 text

Derail – which solution is better? RxJS Signals Modules Standalone Templ. Driven Reactive Forms Default OnPush Nx Sheriff

Slide 72

Slide 72 text

RxJS Subscription Best Practices

Slide 73

Slide 73 text

Manage your RxJS subscriptions • Problem: Components create subscriptions without closing them • Identify: .subscribe() without .unsubscribe() or other methods • Solution: Unsubscribe from all Observables in your App • Except Angular Router Params

Slide 74

Slide 74 text

Why do we (always!) need to unsubscribe? Avoid side effects Avoid memory leaks Also for HttpClient‘s get / post ...

Slide 75

Slide 75 text

RxJS Subscription Management • Explicitly let subscription = observable$.subscribe(…); // subscription.add(otherObservable$.subscribe(…)); // also possible since V6 subscription?.unsubscribe(); • Implicitly • observable$.pipe(takeUntil(otherObservable)).subscribe(…); • observable$.pipe(takeUntilDestroyed()).subscribe(…); • Implicitly with async Pipe in Angular {{ observable$ | async }} • Automatic by Angular • Angular Router Params (the only 1 I know where unsubscribing is not needed) last operator! also triggers a cdr.markForCheck for OnPush

Slide 76

Slide 76 text

Where do we subscribe? • 1 Field initializer • 2 Constructor • 3 If @Input(s) needed → ngOnInit hook (needs injected destroyRef) • 4 Elsewhere (needs injected destroyRef)

Slide 77

Slide 77 text

Demo Unsubscribing

Slide 78

Slide 78 text

Lab 03b Further Runtime Performance

Slide 79

Slide 79 text

Recap • Use track in @for if possible • Avoid large component trees • Use Spinners and preview thumbs • Optimistic updates • Bonus: RxJS Subscription Management

Slide 80

Slide 80 text

Questions?

Slide 81

Slide 81 text

References • Angular CDK Scrolling Comp • https://material.angular.io/cdk/scrolling/overview