Angular traditionally relies on Zone.js to perform change detection automatically. Zone.js monkey-patches browser APIs like setTimeout, addEventListener, and Promise to know when to trigger change detection after an asynchronous task completes.

If your Angular app does not use Zone for change detection, they are called Zone-free applications.Instead, they explicitly manage when and how change detection should be triggered — giving better control over performance and reactivity.

This approach is often referred to as:

  • Zone-less Angular
  • Manual change detection
  • Signal-based change detection (with Angular Signals)
  • Using ChangeDetectorRef or NgZone.run()

Why Go Zone-Free?

BenefitDescription
Manual controlDevelopers decide when change detection should run.
PerformanceEliminates non-compulsion checks and boosts performance in large apps.
PredictabilityMakes async flows and state updates more deterministic.
Fits with SignalsEncourages use of Angular Signals, which are reactive primitives introduced in Angular 16+.

How Angular Detects Changes With and Without Zone.js?

With Zone.jsWithout Zone.js
Monkey-patches browser APIsNo monkey-patching
Automatically triggers change detectionYou must manually trigger change detection
Easy for beginnersBetter for experienced devs who want control

Enabling Zone-Free Angular App

Opt out of Zone.js

In your main.ts, remove import ‘zone.js’.
Instead, use bootstrapApplication() with the zone: ‘noop’ flag.

import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, { providers: [], // Disable Zone.js zone: 'noop'
});

Note: zone: ‘noop’ is a special Angular mode that disables Zone.js integration.

Example: Zone-Free Angular App with Signals

Here’s a minimal zone-free Angular app using Signals (introduced in Angular 16).

app.component.ts (with signals)
import { Component, computed, signal } from '@angular/core';
@Component({ selector: 'app-root', standalone: true, template: ` <h2>Zone-Free Counter</h2> <p>Count: {{ count() }}</p> <button (click)="increment()">Increment</button> <p>Doubled: {{ doubleCount() }}</p> `
})
export class AppComponent { count = signal(0); doubleCount = computed(() => this.count() * 2); increment() { this.count.update(c => c + 1); }
}
main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, { zone: 'noop' // Disable zone.js
});

No Zone.js Installed
Make sure you don’t install or import zone.js in your project.

Key Concepts Without Zone.js

Signals

  • Are reactive primitives.
  • Automatically track dependencies.
  • Efficient and predictable.
  • Compatible with zone-free architecture.

ChangeDetectorRef

If you’re not using Signals, and you’re manipulating the DOM or state manually, you may need to inject ChangeDetectorRef and call markForCheck() or detectChanges().

constructor(private cdr: ChangeDetectorRef) {}
someAsyncCall() { this.service.getData().subscribe(data => { this.data = data; this.cdr.detectChanges(); // Manually trigger detection });
}

Challenges

  • Not beginner-friendly: You must handle updates explicitly.
  • More manual work: You need to manage change detection using signals or ChangeDetectorRef.
  • Some libraries assume Zone.js is present.

When Should You Use Zone-Free Angular?

Use Zone-Free If…Stick With Zone.js If…
You care about perf & controlYou want simplicity
You use SignalsYou’re using older Angular features
You’re building enterprise-level, reactive UIsYou’re prototyping or learning Angular