0 day streak
javascript / expert
Snippet

Zone-less Change Detection with Signal-Driven Component Boundaries

Without Zone.js, Angular no longer patches every async API (setTimeout, DOM events, promises) to trigger change detection automatically — `provideExperimentalZonelessChangeDetection` removes that global monkey-patching entirely. Change detection is instead scheduled only when a signal read inside a template actually changes value, tracked via the reactive graph that `computed` and `signal` build. Any state mutation that bypasses signals — mutating a plain object property and expecting the view to notice — becomes permanently invisible to the view, since there is no zone left to catch it as a fallback, which is exactly the trade-off expert Angular work needs to reason about explicitly.

snippet.js
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { bootstrapApplication } from '@angular/platform-browser';
import { provideExperimentalZonelessChangeDetection } from '@angular/core';
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-price-badge',
standalone: true,
template: `<span>{{ formatted() }}</span>`,
})
export class PriceBadgeComponent {
private cents = signal(0);
formatted = computed(() =>
new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' })
.format(this.cents() / 100),
);
setCents(value: number): void {
this.cents.set(value);
}
}
bootstrapApplication(PriceBadgeComponent, {
providers: [provideExperimentalZonelessChangeDetection()],
});
angular
Breakdown
1
provideExperimentalZonelessChangeDetection()
Removes Zone.js patching entirely; change detection is now driven solely by signal reads inside templates, not by intercepted async APIs.
2
private cents = signal(0);
A writable signal — the only kind of state mutation that the zoneless scheduler can actually observe and react to.
3
formatted = computed(() => ...)
A derived signal that only recomputes when `cents` changes, and only re-renders the template if its own output value differs.
4
setCents(value: number): void { this.cents.set(value); }
The correct mutation path in a zoneless app — calling `.set()` is what actually schedules a re-render; direct property assignment would not.