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?
Benefit | Description |
Manual control | Developers decide when change detection should run. |
Performance | Eliminates non-compulsion checks and boosts performance in large apps. |
Predictability | Makes async flows and state updates more deterministic. |
Fits with Signals | Encourages use of Angular Signals, which are reactive primitives introduced in Angular 16+. |
How Angular Detects Changes With and Without Zone.js?
With Zone.js | Without Zone.js |
Monkey-patches browser APIs | No monkey-patching |
Automatically triggers change detection | You must manually trigger change detection |
Easy for beginners | Better 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 & control | You want simplicity |
You use Signals | You’re using older Angular features |
You’re building enterprise-level, reactive UIs | You’re prototyping or learning Angular |