Zoneless Change Detection
Angular 21 default — No zone.js, no monkey-patching, just signals
About This Feature
In Angular 21, new applications are zoneless by default. Zone.js is no longer required. Angular 21 uses signal-based change detection instead of zone-based change detection.
- Zone.js worked by monkey-patching browser APIs to detect any async operation and trigger full change detection. This caused unnecessary re-renders and hurt Core Web Vitals (especially INP — Interaction to Next Paint).
- Zoneless tracks signal dependencies at the component level. Only components that read a changed signal re-render — fine-grained, efficient.
- ~35KB bundle reduction from removing zone.js. Faster startup. Simpler mental model.
- Enabled via
provideZonelessChangeDetection()— automatic in Angular 21 new projects.
Configuration
// app.config.ts — Angular 21 new projects: zoneless by default
import { ApplicationConfig, provideZonelessChangeDetection } from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
// Angular 21 new apps include this automatically
provideZonelessChangeDetection(),
// If you want zone.js instead (for existing code):
// provideZoneChangeDetection({ eventCoalescing: true })
]
};
// package.json — zone.js is no longer a required dependency
// "zone.js" can be removed from polyfills in angular.jsonWhy Zoneless?
// ── The problem with Zone.js ────────────────────────────
// Zone.js monkey-patches browser APIs (setTimeout, fetch, Promises,
// event listeners...) to trigger Angular change detection after
// every async operation. This causes:
// ❌ Unnecessary re-renders
// ❌ Slower Interaction-to-Next-Paint (INP) / Core Web Vitals
// ❌ Hidden performance pitfalls
// ❌ Large bundle size (~35kb)
// ── How zoneless works ───────────────────────────────────
// Angular tracks which signals are used in each component's template.
// When a signal changes, only affected components re-render.
// No monkey-patching. No unnecessary checks.
// ── What triggers change detection in zoneless ───────────
signal.set(value) // ✅ Automatic — signal change triggers re-render
signal.update(fn) // ✅ Automatic
computed(() => ...) // ✅ Automatic — derived signal
async pipe {{ '{{' }} obs$ | async {{ '}}' }} // ✅ Automatic
// Manual trigger (rarely needed):
inject(ChangeDetectorRef).markForCheck();Live Demo — Pure Signal Reactivity (no zone.js needed)
This demo runs in zoneless mode. All UI updates are driven purely by signal changes — no Zone.js involved:
count() = 0 | computed doubled = 0
computed greeting: Hello, Angular!
Signal update log
Click the buttons to see signal updates…
✓ No Zone.js needed — every update above is driven by
signal.set() / signal.update() / computed() only. Angular knows exactly which template expressions changed and re-renders only those. Zone.js vs Zoneless at a glance
| Aspect | Zone.js (pre-21) | Zoneless (Angular 21+) |
|---|---|---|
| Bundle size | +~35KB (zone.js) | 0KB overhead |
| Change detection trigger | Any async event (over-triggers) | Signal changes only (precise) |
| Core Web Vitals (INP) | Often hurt by excessive CD | Improved by up to 30% |
| Required for reactivity | Zone.js monkey-patching | Signals + async pipe |
| Default in Angular 21 | No (opt-in) | Yes |