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.json

Why 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

AspectZone.js (pre-21)Zoneless (Angular 21+)
Bundle size+~35KB (zone.js)0KB overhead
Change detection triggerAny async event (over-triggers)Signal changes only (precise)
Core Web Vitals (INP)Often hurt by excessive CDImproved by up to 30%
Required for reactivityZone.js monkey-patchingSignals + async pipe
Default in Angular 21No (opt-in)Yes