Angular Change Detection Strategies: Default vs. OnPush
Change detection is a critical aspect of Angular’s data binding mechanism. It determines when and how Angular updates the user interface based on changes in the application’s data. Angular offers different change detection strategies to optimize performance and control the update process.
In this article, we’ll explore two popular change detection strategies: Default and OnPush. We’ll discuss their characteristics, advantages, and use cases, and provide a real-world example to demonstrate their behavior. Let’s dive in!
Default Change Detection Strategy
The Default change detection strategy is the default behavior used by Angular. It checks all components for changes during each cycle of change detection. When a component’s input properties or its associated template bindings change, Angular triggers a re-render of that component and all of its child components.
Example: Parent and Child Components with Default Strategy
Let’s consider a simple scenario where we have a parent component and two child components: ChildDefaultComponent
and ChildOnPushComponent
. The parent component has a counter, and clicking a button increments the counter.
// Parent Component
@Component({
selector: 'app-parent',
template: `
<h2>Parent Component</h2>
<button (click)="increment()">Increment Counter</button>
<app-child-default [counter]="counter"></app-child-default>
<app-child-onpush [counter]="counter"></app-child-onpush>
`,
})
export class ParentComponent {
counter = 0;
increment() {
this.counter++;
}
}
// Child Component with Default Strategy
@Component({
selector: 'app-child-default',
template: `
<h4>Child Component (Default)</h4>
<p>Counter: {{ counter }}</p>
<p>Timer: {{ timer }}</p>
`,
})
export class ChildDefaultComponent implements OnInit, OnDestroy {
@Input() counter!: number;
timer: number = 0;
private intervalId: any;
ngOnInit() {
this.intervalId = setInterval(() => {
this.timer++;
}, 1000);
}
ngOnDestroy() {
clearInterval(this.intervalId);
}
}
In this example, both child components receive the counter
input property from the parent component. Additionally, both child components have a timer
property that increments every second using a setInterval
function in the ngOnInit
lifecycle hook.
The Default change detection strategy ensures that both the counter
and timer
properties in ChildDefaultComponent
trigger re-renders whenever they change. So, when we click the “Increment Counter” button in the parent component, both child components will be re-rendered, updating the displayed counter and timer values.
In the Default strategy, changes in the parent component trigger the change detection process for all its child components and their descendants. This includes re-rendering all components, regardless of whether the changes directly affect them or not.
OnPush Change Detection Strategy
The OnPush change detection strategy is designed to optimize performance by reducing the number of components that require re-rendering. With this strategy, Angular only checks components for changes when their input properties change or when an event is fired from within the component itself.
Example: Parent and Child Components with OnPush Strategy
Now let’s modify our previous example to use the OnPush change detection strategy in the ChildOnPushComponent
. This change will demonstrate how the OnPush strategy avoids unnecessary re-renders when input properties don’t change.
// Child Component with OnPush Strategy
@Component({
selector: 'app-child-onpush',
template: `
<h4>Child Component (OnPush)</h4>
<p>Counter: {{ counter }}</p>
<p>Timer: {{ timer }}</p>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ChildOnPushComponent implements OnInit, OnDestroy {
@Input() counter!: number;
timer: number = 0;
private intervalId: any;
ngOnInit() {
this.intervalId = setInterval(() => {
this.timer++;
}, 1000);
}
ngOnDestroy() {
clearInterval(this.intervalId);
}
}
With the OnPush strategy applied to ChildOnPushComponent
, the timer
property will not trigger a re-render when the counter
changes. The timer
property is not directly affected by changes in input properties, so it doesn’t require re-evaluation.
The sequence diagram illustrates the interaction between different components in an Angular application, specifically focusing on the change detection strategy. Let’s break it down step by step:
In this diagram, the Parent
component triggers input or event changes to both ChildDefault
and ChildOnPush
components. With the default change detection strategy, all changes in the counter
and timer
properties will trigger re-renders in both ChildDefault
and GrandchildDefault
components.
With the OnPush change detection strategy, the counter
property changes in ChildOnPush
component will trigger re-rendering, but the timer
property will not. Additionally, the counter
change in ChildOnPush
component will be passed to GrandchildOnPush
component, triggering a re-render there as well.
Conclusion
Understanding Angular’s change detection strategies is essential for optimizing performance in your applications. By leveraging the Default strategy, you ensure that all components are checked for changes and re-rendered as needed. On the other hand, the OnPush strategy allows you to limit the re-rendering process to components that are directly affected by changes, resulting in improved performance.
By applying the appropriate change detection strategy based on your application’s requirements, you can achieve better performance and more efficient updates to the user interface.