Understanding OnPush Change Detection

In the previous discussion on change detection, we covered the fundamentals. Now, let's dive deeper into the ChangeDetectionStrategy.OnPush approach. Let's begin.

Configuring OnPush

The first step is to see how a component can be switched to the OnPush strategy. Consider a Sample component that currently uses the default change detection—which is what every newly created component gets by default. It looks something like this:

import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-sample',
  templateUrl: './sample.component.html',
  styleUrls: ['./sample.component.scss']
})
export class SampleComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }

}
Enter fullscreen mode Exit fullscreen mode

Now, to switch this component to OnPush, you add a property called changeDetection to the component metadata, assigning it the value ChangeDetectionStrategy.OnPush.
If you inspect the possible values for this strategy using your IDE, you'll notice there's also Default—which you don't have to explicitly set if you prefer the standard change detection behavior.

image

After making this adjustment, the component code looks like this:

import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-sample',
  templateUrl: './sample.component.html',
  styleUrls: ['./sample.component.scss'],
  changeDetection:ChangeDetectionStrategy.OnPush
})
export class SampleComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }

}
Enter fullscreen mode Exit fullscreen mode

With that change in place, let's explore the conditions under which this component will now re-render.

When Does Change Detection Trigger for OnPush Components?

Once the OnPush strategy is applied, the component no longer re-renders on every change detection cycle. Instead, it only updates when an @Input property is modified from the parent, or when an event or parameter changes within the component itself—which also re-renders its child components.
Let's demonstrate this with an example. The complete code is available on Stackblitz. I'll pull relevant snippets from there.
Imagine a SampleComponent that contains a child component called SampleChild, which is configured with OnPush.
First, let's try modifying the input object in a mutable way—we'll change a value within the object directly.

sample.component

<button (click)="valueChange()" >
  Change input to 5
</button>
<app-samplechild [data]="data" ></app-samplechild>
Enter fullscreen mode Exit fullscreen mode
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-sample',
  templateUrl: './sample.component.html',
  styleUrls: ['./sample.component.scss']
})
export class SampleComponent implements OnInit {
  data={value:1};
  constructor() { }

  ngOnInit(): void {
  }
  valueChange(){
    this.data.value=5;
  }
}
Enter fullscreen mode Exit fullscreen mode

samplechild.component

<p>
  The value from parent is
  {{data.value}}
</p>
<p>

  {{whenComponentRerendered()}}
</p>
Enter fullscreen mode Exit fullscreen mode
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';

@Component({
  selector: 'app-samplechild',
  templateUrl: './samplechild.component.html',
  styleUrls: ['./samplechild.component.scss'],
  changeDetection:ChangeDetectionStrategy.OnPush
})
export class SamplechildComponent implements OnInit {

  @Input('data') data!:any;
  constructor() { }

  ngOnInit(): void {
  }
  whenComponentRerendered(){
    console.log('component rerendered');
  }
}
Enter fullscreen mode Exit fullscreen mode

You'll observe that even after clicking the button to change the input, the value in the child component doesn't update—it simply won't re-render because of the OnPush strategy. To verify this, try switching OnPush back to Default, and you'll see the child component's value update as expected when the change detection runs.

Now, how can we update the child component's value without abandoning the OnPush strategy? The key rule is to pass input objects in an immutable manner. Instead of mutating the object directly, you should create and pass a new object reference. Let's adjust the parent component's code accordingly.

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-sample',
  templateUrl: './sample.component.html',
  styleUrls: ['./sample.component.scss']
})
export class SampleComponent implements OnInit {
  data={value:1};
  constructor() { }

  ngOnInit(): void {
  }
  valueChange(){
    this.data={
      ...this.data,
      value:5
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, with the immutable approach to passing objects, the view will correctly display the updated value of 5.
This also shows that change detection won't run in the child component just because the parent is being checked. To illustrate this, I've added a simple button that logs to the console, along with another function that logs whenever a component re-renders—for both the parent and child. When you click that new button, you'll see the parent re-rendering but not the child, as shown in the screenshot below.
image

Similarly, the child component will run change detection if an event or change originates from within itself—for instance, when we directly modify a value inside the child. Let's add that functionality to the child component's code.

<p>
  The value from parent is
  {{ data.value }}
</p>
<p>
  {{ whenComponentRerendered() }}
</p>
<button (click)="changeValue()">Change button from child component</button>
Enter fullscreen mode Exit fullscreen mode
import {
  ChangeDetectionStrategy,
  Component,
  Input,
  OnInit,
} from '@angular/core';

@Component({
  selector: 'app-samplechild',
  templateUrl: './samplechild.component.html',
  styleUrls: ['./samplechild.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SamplechildComponent implements OnInit {
  @Input('data') data!: any;
  constructor() {}

  ngOnInit(): void {}
  whenComponentRerendered() {
    console.log('child component rerendered');
  }
  changeValue() {
    this.data.value = 5;
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, when you click the button inside the child component, that component will re-render, and you can confirm this by checking the console output.
image

In this article, we've covered how to implement the OnPush change detection strategy for a component and the specific conditions that trigger its change detection. In the next installment of this series, we'll look at other ways to take manual control of change detection. Thanks for reading.

If you found this useful, share it with your friends. For any suggestions, you can reach me on Twitter or leave a comment below.
Until next time, happy learning!