0%

angular-zoneless-change-detection

Introduction

Zoneless change detection是Angular 18引入的一个新特性,该特性能让Angular在不依赖zone.js的情况下进行更新检测。下面我们来看一下如何使用这个新特性。

Why remove Zone.js?

  1. Reducing initial bundle size, Zone.js is about 30KB raw and around 10KB gzipped. Remove it can significantly save the initial load time.
  2. Avoid unnecessary change detection cycles: Zone.js notify Angular to run change detection on every async operation, but it doesn’t actually know whether these operations change any data.
  3. Improve debugging experience, Zone.js can make stack traces harder to read.

Integration steps

Create an Angular 18 project

1
npx @angular/cli@18 new my-zoneless-app

Enable zoneless change detection in standalone bootstrap app.

  1. Open file app.config.ts under src/app folder.
  2. Remove the following line
    1
    provideZoneChangeDetection({ eventCoalescing: true }),
  3. Add the following line
    1
    provideExperimentalZonelessChangeDetection(),

Enable zoneless change detection in traditional Module based app.

1
2
3
4
5
6
// NgModule bootstrap
platformBrowser().bootstrapModule(AppModule);
@NgModule({
providers: [provideExperimentalZonelessChangeDetection()]
})
export class AppModule {}

Remove zone.js from your project

Remove zone.js from file angular.json or project.json for Nx based project.

  1. Open file angular.json or project.json.
  2. Remove the following line from architect | build | options
    1
    2
    3
    "polyfills": [
    "zone.js"
    ],
  3. Remove the following line from architect | test | options
    1
    2
    3
    4
    "polyfills": [
    "zone.js",
    "zone.js/testing"
    ],

Old app with polyfills.ts

For old Angular app with an explicit polyfill.ts file, you can remove import 'zone.js' and import 'zone.js/testing' from polyfills.ts.

Uninstall zone.js

1
npm uninstall zone.js

Start your app

1
ng serve # or npm run start

How to make your app work with zoneless change detection

对于开启了zoneless change detection的应用,Angular需要依赖核心API的通知才能进行更新检测,这些通知包括:

  • ChangeDetectorRef.markForCheck (called automatically by AsyncPipe)
  • ComponentRef.setInput
  • Updating a signal that’s read in a template
  • Bound host or template listeners callbacks
  • Attaching a view that was marked dirty by one of the above

除了以上几种情况,Angular不会自动进行更新检测,比如setTimeoutsetIntervalPromise.thenfetch等异步操作,这些操作不会触发更新检测,需要手动调用ChangeDetectorRef.markForCheck来通知Angular进行更新检测。

Use signal for simple value

1
2
3
4
5
6
7
8
// app.component.ts
name = signal('zdd');

ngOnInit() {
setTimeout(() => {
this.name.set('Philip')
}, 1000)
}

Note that, you need to use name() instead of name in your template.

1
2
<!-- app.component.html -->
<p>Hello, I'm {{name()}}</p>

Use signal for complex value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// app.component.ts
interface User {
id: string;
title: string;
}

user = signal<User | null>(null);

fetchData() {
return fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
}

ngOnInit() {
this.fetchData().then(res => {
this.user.set(res);
});
}

Note again, you need to use user() instead of user in your template.

1
2
3
4
5
<!-- app.component.html -->
<div>
user id: {{user()?.id}}
user name: {{user()?.title}}
</div>

Manually call cdf.markForCheck

Note, setTimeout won’t call change detection automatically, you need to call cdf.markForCheck manually.

1
2
3
4
5
6
7
8
9
constructor(private cdf: ChangeDetectorRef) {}
name = 'zdd'

ngOnInit() {
setTimeout(() => {
this.name = 'Philip';
this.cdf.markForCheck();
}, 1000)
}

for fetch api, you need to call cdf.markForCheck manually as well.

1
2
3
4
5
6
7
8
9
10
11
fetchData() {
return fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
}

ngOnInit() {
this.fetchData().then(res => {
this.user = res;
this.cdf.markForCheck();
});
}

References

  1. https://angular.dev/guide/experimental/zoneless
  2. https://angular.love/the-latest-in-angular-change-detection-zoneless-signals