1 of 51

Get ready to go Zoneless

angularday 2024 | Verona | November 8

2 of 51

3 of 51

  1. What is Change Detection? How does it work with Zone.js?

  • What does removing Zone.js mean for Angular?�How does Angular Change Detection work when going Zoneless?

  • How can you "Get ready to go Zoneless"?�What parts of the codebase need refactoring?

4 of 51

let count = 1;

count = 5;

let count = 1;

⚡️

⚡️

⚡️

⚡️

⚡️

5 of 51

@Component({

selector: "my-component",

template: `

<div>{{ counter }}</div>

<button (click)="increase()"></button>

`,

})

export class MyComponent {

count = 0;

increase(): void {

this.count++;

}

}

@Component({

selector: "my-component",

template: `

<div>{{ counter }}</div>

<button (click)="increase()"></button>

`,

})

export class MyComponent {

count = 0;

increase(): void {

setInterval(() => {

this.count++;

}, 1000);

}

}

Zone.js

Z

Powered by

6 of 51

Monkey patching is a technique to modify or extend existing code

at runtime without altering the original source code

This triggers Angular change detection

Zone.js

Z

const originalSetTimeout = window.setTimeout;

window.setTimeout = function (fn, ms) {

originalSetTimeout(fn, ms);

this.applicationRef._tick();

};

originalSetTimeout(..., ...);

7 of 51

MacroTask

MicroTask

EventTask

  • setTimeout( )
  • clearTimeout( )
  • setInterval( )
  • ...
  • Promise
  • MutationObserver
  • ...
  • DOM event
  • Event listeners
  • ...

Zone.js

Z

8 of 51

AppComponent

ApplicationRef

9 of 51

NgZone

AppComponent

ApplicationRef

ApplicationRef

AppComponent

Event

10 of 51

<img

[alt]="hero.name"

[src]="hero.imageUrl"

/>

<app-hero-detail

[hero]="hero"

/>

<main>

<h2>Hello {{ firstName }}!</h2>

<input type="text" [(ngModel)]="firstName" />

</main>

@if(show){

@for (item of list; track item.id) {

<p>{{ item.name }}</p>

<img [src]="item.image" />

}

}

11 of 51

Angular default change detection

12 of 51

  1. Input values update
  2. Angular triggers an event using event binding, output binding or @HostListener
  3. Marked as dirty using the method changeDetectorRef.markForCheck( )

13 of 51

NgZone

OnPush

AppComponent

ApplicationRef

ApplicationRef

AppComponent

Event

OnPush

14 of 51

NgZone

OnPush

AppComponent

ApplicationRef

ApplicationRef

Event

OnPush

OnPush

AppComponent

AppComponent

15 of 51

Let's migrate every component to

OnPush startegyyy

16 of 51

@Component({

selector: 'my-component',

template: `

<button (click)="addItem()">Add</button>

<my-child [items]="items" />

`

})

export class MyComponent {

items = [];

addItem() {

this.items.push(42);

this.items = [...this.items, 42];

}

}

@Component({

selector: 'my-component',

template: `

<button (click)="loadData()">Load</button>

<my-child [items]="items" />

`

})

export class MyComponent {

cdr = inject(ChangeDetectorRef);

myService = inject(MyService);

items = [];

loadData() {

this.myService.getData().subscribe(data => {

this.items = data;

this.cdr.markForCheck();

})

}

}

17 of 51

Spread operators and markForCheck( )

EVERYWHERE

18 of 51

Default

OnPush

Checks all components binded values on every

event or model change

Skip checks unless

  • input values change
  • internal events occur
  • markForCheck( )

Performance

DevEx

19 of 51

20 of 51

const name = signal('Mario');

const lastName = signal('Rossi');

firstName.set('Giorgio');

firstName.set('Matteo');

const fullName = computed(() => `${name()} ${lastName()}`);

effect(() => {

console.log(`Full Name: ${fullName()}`);

});

21 of 51

Angular templates can now track

Signals updates using effect( )

<div>

{{ description() }}

</div>

<app-hero-detail

[hero]="hero()"

/>

@if(show()){

@for (item of list(); track item) {

<p>{{ item.name }}</p>

<img [src]="item.image" />

}

}

22 of 51

NgZone

OnPush

Signal update

AppComponent

ApplicationRef

ApplicationRef

OnPush

OnPush

OnPush

AppComponent

AppComponent

OnPush

Global Mode

Targeted Mode

23 of 51

This is great...

BUT

Zone.js

Z

24 of 51

Zone on, Zone off

25 of 51

Circa 15 minuti

fino a qui

26 of 51

27 of 51

NgZone

ApplicationRef

AppComponent

28 of 51

AppComponent

ApplicationRef

ChangeDetectionScheduler

29 of 51

Without Zone.js, Angular can only rely on its own APIs

These APIs include:

  • ChangeDetectorRef.markForCheck( )
  • Updating a Signal that is read in a template
  • Host or template listeners are triggered
  • ComponentRef.setInput( )
  • Attaching and detaching a view
  • Registering a render hook
  • AsyncAnimationLoad
  • Deferrable views updates

ChangeDetectionScheduler

30 of 51

Without Zone.js, Angular can only rely on its own APIs

These APIs include:

  • ChangeDetectorRef.markForCheck( )
  • Updating a Signal that is read in a template
  • Host or template listeners are triggered
  • ComponentRef.setInput( )
  • Attaching and detaching a view
  • Registering a render hook
  • AsyncAnimationLoad
  • Deferrable views updates

This triggers Angular change detection starting from v18

ChangeDetectionScheduler

31 of 51

New experimental Zoneless change detection provider

export const appConfig: ApplicationConfig = {

providers: [

provideExperimentalZonelessChangeDetection(),

...

],

};

32 of 51

{

"name": "my-app",

"dependencies": {

"@angular/compiler": "^18.0.0",

"@angular/core": "^18.0.0",

...,

"rxjs": "~7.8.0",

"tslib": "^2.3.0",

"zone.js": "~0.14.3"

}

}

{

"projects": {

"my-app": {

"architect": {

"build": {

"options": {

"polyfills": [

"zone.js"

],

},

...

{

"projects": {

"my-app": {

"architect": {

"build": {

"options": {

"polyfills": [

"zone.js"

"zone.js/testing"

],

},

...

33 of 51

Bye Bye Zone.js

34 of 51

App not working

35 of 51

TIME TO REFACTOR

36 of 51

Demo structure:

  • Migration to Zoneless
  • Refactor the app where necessary
  • Reintegrate Zone as a demonstration of the compatibility of our changes

Grusp

37 of 51

🚀 DEMO 🚀

38 of 51

Steps to Get ready to go Zoneless:

  • Migrate each component to OnPush change detection
    • Using markForCheck( ) and AsyncPipe
    • Using Signals on your templates

  • Replace NgZone onStable( ) and onMicrotaskEmpty( ) methods�with afterRender( ) and afterNextRender( )
  • Zoneless only: remove NgZone run*( ) methods and just call your function

39 of 51

Steps to Get ready to go Zoneless:

  • Migrate each component to OnPush change detection
    • Using markForCheck( ) and AsyncPipe
    • Using Signals on your templates

  • Replace NgZone onStable( ) and onMicrotaskEmpty( ) methods�with afterRender( ) and afterNextRender( )
  • Zoneless only: remove NgZone run*( ) methods and just call your function

40 of 51

Steps to Get ready to go Zoneless:

  • Migrate each component to OnPush change detection
    • Using markForCheck( ) and AsyncPipe
    • Using Signals on your templates

  • Replace NgZone onStable( ) and onMicrotaskEmpty( ) methods�with afterRender( ) and afterNextRender( )
  • Zoneless only: remove NgZone run*( ) methods and just call your function

41 of 51

Embrace the Signals APIs

42 of 51

@Component({ ... })

export class MyComponent {

myInputEl = viewChildren('inputEl'); // Signal<readonly ElementRef[]>

myContentEl = contentChild('contentEl'); // Signal<ElementRef | undefined>

myProp = input<string>(); // InputSignal<string, string>

myRequiredProp = input.required<string>(); // InputSignal<string, string>

myModel = model<string>(); // ModelSignal<string>

myRequiredModel = model.required<string>(); // ModelSignal<string>

}

43 of 51

44 of 51

OnPush

Skip checks unless

  • input values change
  • internal events occur
  • markForCheck( )

Performance

DevEx

Default

Checks all components binded values on every

event or model change

45 of 51

Bye bye Zone.js� �Easy to dev and highly optimized with Signals

Zoneless

DevEx

Performance

46 of 51

47 of 51

Davide Passafaro

  • Senior Frontend Engineer @
  • Angular, NgRx, Capacitor, Electron
  • Angular Rome Community Lead
  • GDG Roma Città Organizer (NEW)

davide-passafaro

DavidePassafaro

@DaveloperIT

48 of 51

49 of 51

50 of 51

export const appConfig: ApplicationConfig = {

providers: [

provideExperimentalZonelessChangeDetection(),

provideExperimentalCheckNoChangesForDebug({

interval: 2000,

// useNgZoneOnStable: true,

exhaustive: true

}),

...

],

};

51 of 51

requestAnimationFrame

setTimeout

ChangeDetectionScheduler