Smart Change Detection in Angular

Change Detectors
All Angular apps are made up of a hierarchical tree of components. At runtime, Angular creates a separate change detector class for every component in the tree, which then eventually forms a hierarchy of change detectors similar to the hierarchy tree of components. Whenever change detection is triggered, Angular walks down this tree of change detectors to determine if any of them have reported changes.
The change detection cycle is always performed once for every detected change and starts from the root change detector and goes all the way down sequentially. This sequential design choice is nice because it updates the model predictably from its parent to the children. However, despite Angular’s efficient change detection process, this approach can lead to performance drawbacks, especially in large projects with numerous components.

NgZone
In order to understand the concept of change detection in Angular, it’s helpful to take a brief look at NgZone
. The NgZone
is a core service provided by Angular, responsible for managing the application's execution context, handling the change detection mechanism, and intercepting asynchronous operations. There exists only one NgZone
, and change detection is exclusively triggered for asynchronous operations initiated within this zone. The NgZone
acts as a vital bridge between Angular's change detection system and the JavaScript execution environment, ensuring efficient tracking and updating of the user interface in response to data changes. This seamless coordination guarantees that the UI remains synchronized with the application's data model, providing a smooth and reactive user experience. The NgZone
in Angular triggers the change detection mechanism on the following events:
- Asynchronous Operations: When an asynchronous operation, such as an HTTP request, timer, or event listener, is initiated within the Angular zone (the zone managed by
NgZone
), the change detection is triggered after the asynchronous task is completed. This ensures that any changes to the application's data model resulting from the asynchronous operation are reflected in the UI. - User Events: User interactions, such as mouse clicks, keyboard inputs, or form submissions, can trigger change detection. Angular automatically triggers change detection after handling these user events, so any modifications to the application’s data are immediately reflected in the UI.
- Promise Resolution: If a promise is resolved within the Angular zone, the change detection mechanism will be triggered. This allows Angular to detect and apply changes once the promise resolves and updates the data.
- Observable Subscriptions: When an observable emits a new value and the subscription is within the Angular zone, change detection will be triggered. This is a crucial mechanism for handling real-time data updates in Angular applications.
- Timers: If a timer is set within the Angular zone and its callback function is executed, change detection will be triggered. Timers are often used to perform periodic tasks or schedule asynchronous operations.
It’s important to note that the NgZone
is responsible for automatically triggering change detection after any of these events occur within its zone. Additionally, change detection can be manually triggered using methods provided by Angular, but this is generally not necessary in most cases as the NgZone
handles it automatically.
NgZone
also provides two methods to execute code outside or explicitly inside the change detection zone:
runOutsideAngular(fn: Function): any
: TherunOutsideAngular()
method is used to execute a function outside the Angular zone. Changes made within this function will not trigger change detection. This is particularly useful when dealing with operations that are not directly related to Angular and do not require updating the view immediately.run(fn: Function): any
: Therun()
method allows you to execute a specified function within the Angular zone. This means that any changes made during the execution of the function will trigger the change detection process.
Change Detection Strategies
In Angular, there are two change detection strategies available, Default (ChangeDetectionStrategy.Default) and OnPush (ChangeDetectionStrategy.OnPush).
By carefully choosing the appropriate change detection strategy for each component, developers can optimize the performance of their Angular applications and minimize unnecessary change detection cycles.
Default Strategy
With the default change detection strategy, Angular avoids making any assumptions about the component’s dependencies. Instead, it meticulously checks every component in the component tree from top to bottom whenever data is mutated or an event is fired. This synchronization occurs within the change detection tree, as shown in the visual, where each component is associated with its respective change detector (CD). These detectors are established during the application bootstrap process.
This detector constantly examines the property’s current value and compares it to its previous value. When a change is detected, it flags the property as isChanged
and sets it to true. In essence, whenever an event is triggered, Angular ensures the complete change detection tree is synchronized.

OnPush Strategy
Using ChangeDetectionStrategy.OnPush
can be beneficial in optimizing the performance of Angular applications, especially for components that don't frequently change or have numerous child components. It reduces unnecessary change detection checks and improves the overall efficiency of the application by limiting change detection to only what is required.
The OnPush instructs the change detection to ignore changes on the outside and run change detection only when any of the following situations happens:
@Input()
reference of the component changes- Reference vs. Value Types: Reference Vs Value — Most People Don’t Understand This
- DOM Event within the component has been dispatched (example click, hover over)
- Emission of an observable event subscribed with Async pipe
- Change detection is manually run

As illustrated in the visual, change detection is not triggered if an event on another component happens. The basic idea behind the OnPush strategy manifests from the realization that if we treat reference types as immutable, we are able to perform change detection based on the question: did the reference change, which is much faster than to check if a value changed.
For example, assume we have an immutable array with 30 elements, and we want to know if there are any changes. We know that, in order for there to be any updates to the immutable array, the reference (on the stack) of it would have to have changed. This means we can initially check to see if the reference to the array is any different, which would potentially save us from doing 30 more checks (in the heap) to determine which element is different. This is called the OnPush strategy.
The change detection OnPush can be enabled by adding ChangeDetectionStrategy.OnPush in its decorator.
import {ChangeDetectionStrategy, Component} from '@angular/core';
@Component({
// ...
changeDetection: ChangeDetectionStrategy.OnPush
})
export class OnPushComponent {
// ...
}
Pure Components
OnPush change detection strategy is a great choice for pure components. A pure component is a type of component that follows certain principles to ensure that its behavior is predictable, isolated, and free of side effects. A pure component is designed to be a “pure function” of its input properties, meaning that its output (the rendered view) is solely determined by its input properties, and it does not modify or rely on any external state.
Characteristics of a Pure Component:
- Input Properties Only: A pure component should rely exclusively on its input properties for rendering its view and performing its logic. It should not access or modify any external state or variables.
- No Side Effects: A pure component should not cause any side effects during its execution. Side effects include modifying shared state, making API calls, changing global variables, etc. The component’s logic should be self-contained and not have any impact beyond its own scope.
- Deterministic: Given the same input properties, a pure component should always produce the same output (view). It should not have hidden dependencies or unpredictable behavior based on external factors.
Design principles for pure components:
- Only use input properties to pass data into the component.
- Avoid modifying input properties directly within the component.
- Do not rely on or modify external state or services within the component.
- Aim for a component design that is focused on rendering the view based on input properties.
Demo
This demo illustrates a comparison between components using the default change detection strategy and components using the OnPush change detection strategy. When clicking on the default child component, you will notice that both the parent and the default child components undergo change detection. However, the OnPush component does not get triggered. Similarly, when an event is triggered on the parent, the OnPush component also does not undergo change detection.
The OnPush component’s change detection is only triggered when there is a change in its input reference or when a click event is triggered from within the component.

Manual Change Detection
In Angular, ChangeDetectorRef
is a built-in service that provides methods to manually trigger change detection for a component and its child components. It allows you to control when and how the change detection mechanism runs, giving you more fine-grained control over the update process. Using ChangeDetectorRef
should be done judiciously and only in specific scenarios where manual control over change detection is required.
ChangeDetectorRef
available methods:
detectChanges()
: This method triggers the change detection process for the component and its child components. It checks for changes in the component's data and updates the view accordingly. Use this method when you need to manually trigger change detection, especially after making changes that Angular's default change detection might not detect automatically.markForCheck()
: This method marks the component and its ancestors as dirty, signaling that a change has occurred and the view needs to be updated during the next change detection cycle. It is a more efficient alternative todetectChanges()
when you want to trigger change detection for a specific component and its parent components.checkNoChanges()
: This method runs the change detection process but throws an error if any changes are detected. It is useful in development and debugging to ensure that there are no unintended changes during the change detection cycle.detach()
: This method allows you to detach the change detector from the change detection tree. When a change detector is detached, it will not be checked during the change detection cycle. This can be useful in scenarios with expensive calculations or operations that are not directly related to the current user interaction or rendering of the view.reattach()
: Thereattach()
method is used to reattach a previously detached change detector back into the change detection tree. After reattaching, the change detector will participate in the next change detection cycle.
Take-Away:
- Complex application: OnPush is especially recommended to be used in big projects (50+ components)
- Pure Components: Choosing the OnPush change detection strategy is ideal when your component’s template and logic rely solely on its input properties and have no side effects. This is particularly suitable for pure components, as they are easier to understand and optimize.
- Directly Mutating Inputs: Avoid using OnPush if you intend to modify a view model passed as input to the component, as OnPush assumes that inputs are immutable and may not trigger the necessary change detection when the view model is updated.