Zone.js has long been utilized by Angular to find asynchronous activities that could need change detection. Angular can plan UI changes without using Zone.js to monitor such activities thanks to zoneless change detection.

This modification offers an alternative perspective on component changes. Developers must comprehend how Signals and RxJS integrate into the paradigm, how Angular gets notifications when an application's state changes, and what current apps should take into account before moving.
Removing Zone.js does not eliminate change detection. Instead, it changes how Angular knows when a view may need to be checked.
In this article, we will explore how zoneless change detection works, examine practical Angular examples, and discuss the implications for application development.
1. Understanding Zoneless Change Detection
Zone.js helps Angular recognize asynchronous operations, including timers, event handlers, and callbacks. These operations can affect application state and may require Angular to update the UI. In a traditional Angular application, Zone.js helps Angular identify asynchronous activity and schedule change detection.
With zoneless change detection, Angular no longer relies on Zone.js to track those operations for scheduling change detection. Instead, Angular uses notifications from supported framework APIs and application code.
Common notification mechanisms include:
- Signals read in templates
- AsyncPipe
- ChangeDetectorRef.markForCheck()
- Bound event handlers and other supported framework mechanisms
The distinction is important: zoneless Angular still performs change detection, but the mechanism used to schedule it changes.
Developers must therefore understand how state updates propagate through components and how Angular is notified when a view needs refreshing.
2. Using Angular Signals in a Zoneless Application
Signals provide a reactive approach to state management. Angular can track signals read in templates and respond when their values change.
Consider the following counter component:
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-counter',
standalone: true,
template: `
<h2>Counter Example</h2>
<p>Current count: {{ count() }}</p>
<button (click)="increment()">Increment</button>
`
})
export class CounterComponent {
count = signal(0);
increment(): void {
this.count.update(value => value + 1);
}
}
The component defines a signal named count. The template reads the signal by calling count(), and the increment() method updates its value.
When the button is clicked, the signal change notifies Angular that the relevant view needs updating.
This example demonstrates how Signals provide an explicit reactive mechanism for managing component state. Angular can respond to tracked signal changes without relying on Zone.js to detect asynchronous activity.
Signals are particularly useful for component state and derived values. However, they do not replace every use case for RxJS, especially when applications need complex event streams, cancellation, or asynchronous data transformations.
3. Working with RxJS and AsyncPipe
RxJS remains useful in zoneless Angular applications. It provides operators and abstractions for handling asynchronous data streams, combining events, managing subscriptions, and transforming API responses. When an Observable supplies data to a template, Angular's AsyncPipe is one supported way to ensure that the view is notified when a new value arrives.
For example:
import { Component } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { Observable, of } from 'rxjs';
@Component({
selector: 'app-user',
standalone: true,
imports: [AsyncPipe],
template: `
<p>User: {{ userName$ | async }}</p>
`
})
export class UserComponent {
userName$: Observable<string> = of('Alex');
}
The AsyncPipe subscribes to the Observable, exposes its latest value to the template, and marks the relevant component for checking when a new value arrives. It also handles subscription cleanup when the view is destroyed.
This makes it a useful option for applications that already rely on RxJS.
Developers do not need to replace every Observable with a Signal when adopting zoneless change detection. The appropriate choice depends on the application's state-management requirements and data flow.
4. When Should You Use ChangeDetectorRef.markForCheck()?
Some applications receive updates from external services, third-party libraries, or custom callbacks.
If an update changes a component property without triggering an appropriate Angular notification, the view may not refresh as expected in a zoneless application.
In such cases, ChangeDetectorRef.markForCheck() can mark the view as needing to be checked.
For example:
import { ChangeDetectorRef, Component } from '@angular/core';
@Component({
selector: 'app-status',
standalone: true,
template: `<p>Status: {{ status }}</p>`
})
export class StatusComponent {
status = 'Waiting';
constructor(
private changeDetectorRef: ChangeDetectorRef
) {}
updateStatus(value: string): void {
this.status = value;
this.changeDetectorRef.markForCheck();
}
}
In this example, the method updates the component property and marks the view for checking. The example illustrates the notification mechanism; it does not mean that every property update requires markForCheck(). If an update already triggers a supported notification, an additional call may be unnecessary.
The correct approach is to understand the source of each update and determine whether Angular is already being notified.
5. How Zoneless Change Detection Affects OnPush Components
The OnPush change-detection strategy is commonly used to help control when Angular checks components.
With OnPush, Angular checks a component under specific conditions, such as when its inputs change, an event is handled in its view, or the component is marked for checking.
Zoneless change detection makes it particularly important to understand these notification paths.
Consider a component that receives data through an input:
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-user-card',
standalone: true,
changeDetection: 0,
template: `<h3>{{ userName }}</h3>`
})
export class UserCardComponent {
@Input() userName = '';
}
For a production component, use the named ChangeDetectionStrategy.OnPush value rather than the numeric placeholder shown in this simplified example:
import {
ChangeDetectionStrategy,
Component,
Input
} from '@angular/core';
@Component({
selector: 'app-user-card',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<h3>{{ userName }}</h3>`
})
export class UserCardComponent {
@Input() userName = '';
}
The second example uses Angular's explicit OnPush configuration.
Developers should avoid mutating shared objects without considering how those changes are communicated to child components. Replacing an input reference when appropriate, using reactive state, and following Angular's notification model makes component updates easier to reason about.
6. Does Removing Zone.js Improve Performance?
Zoneless change detection can reduce overhead associated with Zone.js, but it does not guarantee that every application will become faster.
Performance depends on several factors:
- Component architecture
- State-management patterns
- Rendering frequency
- Template complexity
- JavaScript bundle size
- Network requests and third-party scripts
For example, an application may still have slow rendering because a template performs expensive calculations, a large list renders too many elements, or unnecessary work occurs during UI updates.
Removing Zone.js does not solve these problems automatically.
Developers should measure application performance before and after migration. Browser profiling, Angular DevTools, and realistic user workflows can help identify whether the change improves responsiveness and whether other bottlenecks remain.
Performance optimization should be based on measurements rather than assumptions about the framework configuration.
7. Migration Considerations for Existing Applications
Moving an established Angular application to zoneless change detection requires more than changing its configuration.
Before migrating, developers should review the following areas.
Asynchronous updates: Identify how API responses, timers, callbacks, subscriptions, and external services update component state.
Third-party dependencies: Verify that libraries work with the selected Angular version and do not depend on unavailable Zone.js behavior.
Reactive state: Review where Signals, Observables, and plain component properties are used, and confirm that UI updates follow supported notification paths.
Change-detection strategy: Inspect OnPush components and existing manual change-detection calls. Remove unnecessary calls only after validating the behavior.
Testing: Test forms, dialogs, asynchronous requests, timers, user interactions, and components that integrate with external libraries.
Angular version compatibility: Check the official documentation for the supported zoneless configuration and migration guidance for the application's Angular version.
A gradual migration supported by automated tests is often safer than changing an entire application without validating individual workflows.
Conclusion
Zoneless change detection changes how Angular schedules UI updates without relying on Zone.js to track asynchronous activity. Signals, RxJS, AsyncPipe, ChangeDetectorRef.markForCheck(), and component notification patterns all remain important parts of understanding the model. For developers, the most important task is to understand how application state changes reach Angular and how the framework knows that a view needs updating.
By reviewing asynchronous update paths, validating third-party integrations, testing critical workflows, and measuring performance, teams can make better-informed decisions about adopting zoneless change detection in existing Angular applications.