Zoneless Change Detection in Angular 18
Change detection is important in Angular, ensuring that the UI shows the application’s current state. Traditionally, Angular uses Zone.js to automatically detect and apply these changes to the UI, but with the Angular 18 release, a new approach was introduced—zoneless change detection, as this article will show.
Discover how at OpenReplay.com.
Change detection in Angular involves tracking and updating changes in data components to the UI. It ensures that interaction is displayed in real-time when we interact with an Angular app. This article will take us through change detection, how Zone.js
works, and the reason for the shift to applications without zones. We will also see how to set up a change detection process in a Zoneless application.
Zone.js
works by monkey-patching all async
APIs and creating a zone for each async
task. It will keep track of operations from their beginning to their end. The change detection mechanism is triggered when the task is done, and the UI is updated.
It’s important to note that Angular will not discontinue Zone.js
since many apps have already been made using it. They will continue to support Zone.js
by making critical fixes and security patches.
Why is Angular moving towards Zoneless?
As applications scale, the hands-off reactivity used by Zone.js
begins to show some weaknesses. Some of these challenges are:
Maintenance and performance safeguarding: Maintaining reactivity becomes more challenging as the application grows. This is because Zone.js relies on DOM events and async
tasks to detect changes in the application state, but it doesn’t know whether the state really changed. This causes it to trigger synchronization more often than it is supposed to.
Debugging reactivity: It is difficult to identify the root cause of reactivity problems. With Zone.js, it is challenging to know if the code breaks because it is outside the zone. This is a result of Zone.js` challenging stack trace interpretation.
- Cost of Zones with new Web APIs: Application loading cost and start-up time increase when new Web APIs are added. This will increase the application bundle size. Removing this dependency makes the application more efficient.
Introduction to Zoneless Applications
Zoneless applications do not use Zone.js for change detection. Rather, the component itself notifies Angular’s change detection mechanism to update the UI after the completion of an async
function.
It gives developers additional flexibility over the timing and mechanism of change detection, improving the application’s performance and efficiency.
We will demonstrate with some examples without Zone.js
that require change detection. To follow up on this tutorial, first install Angular 18 by running the command below.
npm install -g @angular/cli
Run the command below in the command line to create a new Angular project.
ng new <filename>
Open the file in your code editor. After that, disable Zone.js
. We can do this by removing Zone.js
from the polyfills
in the angular.json
file.
"polyfills": [
"zone.js" // remove zone.js
]
Then in the app.config.ts
file, add provideExperimentalZonelessChangeDetection()
in the providers
array. After you’ve done it, you have successfully configured your Angular app to use the Zoneless feature for change detection instead of Zone.js
.
export const appConfig: ApplicationConfig = {
providers: [
provideExperimentalZonelessChangeDetection(),
provideRouter(routes),
],
};
Setting output in the component
After configuring our application to be Zoneless, we will test the change detection mechanism by defining a variable and updating its state.
In the code below, we update the PageNumber
to 2 using the ngOnInit method. Angular’s change detection mechanism detects this change and automatically updates the UI.
import { RouterOutlet } from '@angular/router';
import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, CommonModule],
template: `
<div>Page Number: {{ PageNumber }}</div>
`,
})
export class AppComponent implements OnInit {
title = 'zoneless';
PageNumber = 0;
ngOnInit(): void {
this.PageNumber += 2;
}
}
In the image below, the PageNumber
variable is automatically updated from 0 to 2.
Timer event change
Timer event change in Zoneless isn’t automatically updated. It works like with OnPush as you’ll need to manually trigger it because there’s no automatic change detection as in Zone.js
.
To test this, let’s create a timer event using setTimeout()
to update the PageNumber
to 2 after 1 second.
@Component({
template: `
<div>Page Number: {{ PageNumber }}</div>
`,
})
export class AppComponent implements OnInit {
title = 'zoneless';
PageNumber = 0;
ngOnInit(): void {
setTimeout(() => {
this.PageNumber += 2;
}, 1000);
}
}
The image shows that the PageNumber
variable is not set to 2 after 1 second.
This shows that Angular might not automatically detect asynchronous operations. To initialize it manually, ChangeDetectorRef can be applied to trigger the event handler.
To do this, inject ChangeDetectorRef
into the AppComponent
and use changeDetectorRef.detectChanges()
function to trigger the update.
export class AppComponent implements OnInit {
title = 'zoneless';
changeDetectorRef = inject(ChangeDetectorRef);
PageNumber = 0;
ngOnInit(): void {
setTimeout(() => {
this.PageNumber += 2;
this.changeDetectorRef.detectChanges();
}, 1000);
}
}
The image shows that the PageNumber
variable is updated to 2 after 1 second.
Signal Update
In Zoneless applications, the signal API automatically updates the reactive state to the UI, just as it does in Zone.js.
To test this, let’s create a signal
and initialize it with the value 5. Then update this value by calling the this.signal.set()
after 1s delay.
@Component({
template: `
<div>Signal: {{ signal() }}</div>
`,
})
export class AppComponent implements OnInit {
title = 'zoneless';
signal = signal(5);
ngOnInit(): void {
setTimeout(() => {
this.signal.set(2);
}, 1000);
}
}
The image shows that the signal
is updated from 5 to 2 in the Zoneless application.
Event Change
The Angular detection mechanism detects when a user interacts with elements in the case below a button and triggers an update.
Let’s create a property eventChange
and set it to 0. In the template, we will define a button that, on clicking, will change the value of the event to 200.
@Component({
template: `
<button (click)="eventChange = 200">Click here</button> {{ eventChange }}
`,
})
export class AppComponent {
title = 'zoneless';
eventChange = 0;
}
In the image below, once the button is clicked the eventChange
variable is automatically updated to 200.
Async pipe
The async pipe
subscribes to an Observable
and returns the latest value emitted. When this new value is emitted, the async pipe
automatically updates the UI after triggering the change detection.
In the code below, we create an Observable
time$
that emits a value every second. The async pipe
updates the displayed value, which subscribes to time$
.
@Component({
template: `
<div>Current Time: {{ time$ | async }}</div>
`,
})
export class AppComponent {
time$ = interval(1000); // Emits values every second
}
The image below shows the value of time$
, updated every second.
It is vital to note that Zoneless change detection is still an experimental feature and major updates could be added to its API.
Conclusion
Angular moving towards Zoneless change detection will address some challenges with Zone.js
such as maintenance, performance, and debugging thereby making Angular applications perform more efficiently. We looked through examples where we needed to manually trigger updates using ChangeDetectorRef
, and leverage async pipe
for automatic updates. Zoneless change detection is still experimental but offers a promising alternative for building more scalable applications.
Additional Resources
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.