Why Zoneless Matters
Angular 19 lets you drop Zone.js entirely. That’s a big deal.
Zone.js is Angular’s change detection watchdog — it monkey-patches every async API (setTimeout, Promise, DOM events) to know when something might have changed, then checks the entire component tree. It works, but it’s expensive.
Going zoneless means Angular only re-renders when you explicitly tell it something changed. The result: up to 40% faster rendering in real-world apps.
Setup
Three steps. Five minutes.
1. Create a new project
ng new my-fast-app
# Choose your preferred options when prompted2. Enable zoneless change detection
import { provideExperimentalZonelessChangeDetection } from "@angular/core";
export const appConfig = {
providers: [
provideExperimentalZonelessChangeDetection(),
provideRouter(routes),
provideClientHydration(),
],
};3. Remove Zone.js from polyfills
{
"polyfills": [
// "zone.js" — remove or comment this out
]
}Verify it worked
Open your browser console and type window.Zone. If it returns undefined, Zone.js is gone.
Signals: The Replacement
Without Zone.js, Angular needs another way to know when data changes. That’s Signals — reactive primitives that notify Angular exactly which values changed and which components need updating.
Basic signal
import { Component, signal } from "@angular/core";
@Component({
selector: "app-counter",
template: `
<h2>Count: {{ count() }}</h2>
<button (click)="increment()">Add One</button>
`,
})
export class CounterComponent {
count = signal(0);
increment() {
this.count.update((n) => n + 1);
}
}Call count() to read, .set() to replace, .update() to transform. Angular tracks which templates read which signals and only re-renders those.
Computed values
Derived state that automatically stays in sync:
@Component({
template: `<p>Total: ${{ total() }}</p>`
})
export class PriceComponent {
price = signal(10);
quantity = signal(2);
total = computed(() => this.price() * this.quantity());
}total recalculates only when price or quantity change. No manual subscription management.
Effects
For side effects — things that should happen because a signal changed:
export class ThemeComponent {
theme = signal("light");
constructor() {
effect(() => {
document.body.classList.toggle("dark", this.theme() === "dark");
});
}
}RxJS interop
Already using Observables? Convert them to signals with toSignal:
@Component({
template: `<p>Time: {{ time() }}</p>`,
})
export class ClockComponent {
time = toSignal(interval(1000), { initialValue: 0 });
}