Historically Angular used zone.js to patch async APIs and trigger change detection after every asynchronous task. Zoneless mode removes that patch: change detection runs when a signal changes or when you explicitly notify Angular, which cuts overhead and bundle size.
Before you start
You should be comfortable with change detection and signals. This article assumes a recent Angular version that supports zoneless change detection.
Step-by-step walkthrough
Step 1: Enable zoneless and drop zone.js
Provide zoneless change detection in the bootstrap so Angular no longer depends on zone.js, and remove zone.js from the polyfills. The build gets smaller, and updates are driven by signal writes rather than by patched async events.
Step 2: Drive updates with signals
A signal write notifies Angular to check the components that read it, so count.set(count() + 1) schedules an update. Values that are not signals do not schedule anything, so state that should update the view needs to be a signal or you must notify Angular explicitly.
Step 3: Notify Angular for external async sources
For callbacks from libraries that do not use signals, such as a chart’s event or a raw setTimeout, inject ApplicationRef or ChangeDetectorRef and call markForCheck after updating state. This is the replacement for the automatic notification zone.js used to provide.
Worked scenario
The component updates from a plain timer and tells Angular to check.
import { Component, inject, signal, ApplicationRef } from '@angular/core';
@Component({ selector: 'app-clock', template: '{{ ticks() }}', standalone: true })
export class ClockComponent {
private appRef = inject(ApplicationRef);
ticks = signal(0);
constructor() {
setInterval(() => {
this.ticks.update((n) => n + 1);
this.appRef.tick();
}, 1000);
}
}Walk through the example
The signal write marks the component dirty, and appRef.tick() runs change detection for the scheduled work because the interval is not a source Angular tracks. In many cases the signal update alone is enough; the explicit tick covers the non-signal async source, which is precisely the gap zoneless mode leaves.
Common mistake
Removing zone.js but keeping plain mutable fields, so updates never reach the view until something else triggers a check. Another is calling tick() on every event, which re-checks the whole app; prefer signals so only affected components update.
Verify the behavior
Toggle a signal value and confirm the view updates without zone.js. Use a non-signal async callback and confirm the view updates only after markForCheck or tick. Remove zone.js from the polyfills and confirm the build still runs.
Interview exercise
What replaces zone.js as the trigger for change detection in a zoneless app?
Answer and reasoning
Signal writes are the primary trigger: Angular schedules a check for the components that depend on the changed signal. For async sources that are not signals, you notify Angular explicitly with markForCheck or tick. Together they cover the cases zone.js used to handle automatically, but with far less patching and overhead.
Continue learning
Compare update triggers in OnPush and input identity and signal effect scope. Read the Angular zoneless guide and try the Angular interview questions.