How do you run Angular zone-less?
Running Angular applications zone-less means disabling Angular's default integration with Zone.js. This approach can be beneficial for performance-critical applications, debugging, or when integrating with other frameworks that manage their own change detection, but it requires manual handling of change detection.
Understanding Angular Zones
Angular relies on Zone.js to monkey-patch asynchronous browser APIs (like setTimeout, setInterval, XMLHttpRequest, event listeners, etc.) and create a 'zone' of execution. Any asynchronous task initiated within Angular's zone automatically notifies Angular that a potential state change might have occurred, triggering its change detection mechanism. This automation simplifies development but adds overhead.
Why Run Zone-less?
Disabling Zone.js can lead to performance improvements by removing the overhead of zone patches and the numerous microtasks and macrotasks Zone.js tracks. It gives developers explicit control over when change detection runs, which can be useful in applications with very specific performance requirements, complex integrations, or when integrating with libraries that perform their own UI updates and change detection.
How to Run Angular Zone-less
To run an Angular application zone-less, you need to configure the bootstrapApplication function (for standalone applications) or platformBrowserDynamic().bootstrapModule (for NgModule-based applications) to use the noop (no operation) zone. This tells Angular not to integrate with Zone.js.
For Standalone Applications (Angular 14+)
In your main.ts file, when bootstrapping the root component, pass an object with ngZone: 'noop' to the bootstrapApplication function:
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
ngZone: 'noop' // This disables Zone.js integration
})
.catch(err => console.error(err));
For NgModule-based Applications (Older or traditional)
In your main.ts file, you would typically use platformBrowserDynamic().bootstrapModule. You can pass a similar configuration for ngZone:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic()
.bootstrapModule(AppModule, {
ngZone: 'noop' // This disables Zone.js integration
})
.catch(err => console.error(err));
Implications and Manual Change Detection
When running zone-less, Angular will no longer automatically detect changes after asynchronous operations. You will need to explicitly tell Angular when to run change detection. This is typically done using the ChangeDetectionRef service within your components.
Using `ChangeDetectionRef`
Inject ChangeDetectionRef into your components and use its detectChanges() method to force a change detection cycle for the current component and its children, or markForCheck() to mark the component for a check during the next change detection cycle (if one is manually triggered higher up the component tree).
import { Component, ChangeDetectionStrategy, ChangeDetectorRef, OnInit } from '@angular/core';
@Component({
selector: 'app-my-component',
template: `
<p>Current Counter: {{ counter }}</p>
<button (click)="increment()">Increment</button>
`,
changeDetection: ChangeDetectionStrategy.OnPush // Highly recommended with zone-less
})
export class MyComponent implements OnInit {
counter = 0;
constructor(private cdr: ChangeDetectorRef) {}
ngOnInit() {
// Example: Manual update after an async operation
setTimeout(() => {
this.counter = 100; // This change won't be detected automatically
this.cdr.detectChanges(); // Manually trigger change detection
}, 1000);
}
increment() {
this.counter++;
this.cdr.detectChanges(); // Manually trigger change detection after click
}
}
Recommendation: `OnPush` Change Detection
When running zone-less, it is highly recommended to use ChangeDetectionStrategy.OnPush for all components. This strategy aligns well with manual change detection, as it only checks components when their @Input() references change or when an event originates from them, or when detectChanges() or markForCheck() is explicitly called.
Considerations Before Going Zone-less
- Increased boilerplate: You'll need to manually call
detectChanges()ormarkForCheck()wherever data changes occur due to async operations. - Third-party libraries: Some libraries might rely heavily on Zone.js and may not function correctly zone-less without custom wrappers or patches.
- Debugging: Debugging change detection issues can become more complex as the automatic mechanism is removed.
- Performance gains: While possible, measure actual performance gains before committing to a zone-less architecture, as the overhead of Zone.js is often negligible for many applications.