Dai Codes: A Blog

Yet another software engineerʼs blog

A picture of a door handle with the word "push"

Change detection strategies in Angular

A comparison of change detection strategies in Angular and how to use OnPush by default

One of the first things I do when starting any new Angular project is configure the component schematic to generate new components using the OnPush change detection strategy by default:

ng config schematics.@schematics/angular.component.changeDetection OnPush

The above command just adds the following section to the angular.json file:

  "schematics": {
"@schematics/angular": {
"component": {
"changeDetection": "OnPush"
}
}
}

Now whenever I use the Angular CLI to generate a new component, it will be automatically set to use the OnPush change detection strategy:

ng g c some-example

The above command generates this component:

@Component({
// ...
changeDetection: ChangeDetectionStrategy.OnPush // <= We get this bit now by default
})
export class SomeExampleComponent implements OnInit {
// ...
}

So what difference does it make?

Angular needs a way of figuring out whether your component state has changed so that it can update the DOM accordingly. The state change check involves re-evaluating every expression in the component template and comparing the result to the last-seen value for that expression. If the value is different, the DOM is updated with the new value. The ChangeDetectionStrategy for a component controls when this change detection algorithm runs.

The Default (a.k.a., CheckAlways) strategy involves patching several browser APIs (like addEventListener, setTimeout, etc.) to invoke the state change check after each of these are used triggered. This means whenever an event handler is triggered, for example, the change detection routine will run.

Let’s take a look at an example that tries to visualise this.

Counting change detections

As noted, the change detection check involves re-evaluating every expression in the target component template and comparing it to the last value of that expression. So if a component template contains an expression that includes a method call…

  <dt>Change detections</dt>
<dd>{{ cdCount() }}</dd>

…then this method will be invoked every time the component is checked for changes.

If the implementation of this method increments a counter then returns its value…

export class ExampleComponent {

private cdCounter = 0;

cdCount() {
return ++this.cdCounter;
}

}

…then every time Angular checks the component for changes, we will see the counter increase in the rendered HTML.

Change detection using the default strategy

The default change detection strategy invokes the change check algorithm in response to many things, including the triggering of an event handler.

So let’s add a button with a click event handler to our component:

<p>
<button type="button" (click)="btnClick.emit()">
Click Me!
</button>
</p>
<dl>
<dt>Clicks</dt>
<dd>{{clickCount}}</dd>
<dt>Change detections</dt>
<dd>{{cdCount()}}</dd>
</dl>

And in our component class, let’s add an output which emits when the button is clicked, and an input for the parent component to pass in a count of how many times the output has emitted:

@Component({
selector: 'app-example-component',
templateUrl: './example.component.html',
styleUrls: ['./example.component.css'],
})
export class ExampleComponent {
@Input()
clickCount: number;

@Output()
readonly btnClick = new EventEmitter<void>()

private cdCounter = 0;

cdCount() {
return ++this.cdCounter;
}
}

In our parent component, we’ll display a bunch of these example components, along with another button which also has a click listener.

The result is rendered below. Try clicking a few of the various buttons in the frame to see what happens:

Notice how every component is checked for changes after any button is clicked. There may not be a noticeable performance hit in this trivial example, but imagine the level of wasted effort Angular is exerting for a large application with hundreds of components that collectively have myriad expressions in their templates that each need to be reevaluated after every event handler (or timeout, or interval, etc.) emits.

Change detection using the OnPush strategy

Let’s see what happens if we change our component to use the OnPush change detection strategy:

@Component({
// snip ...
changeDetection: ChangeDetectionStrategy.OnPush // <== We’ve added this line
})
export class ExampleComponent {
// snip ...
}

Go ahead and try clicking a few of the buttons again:

Now component instances which are unaffected by the click events do not have their change detector invoked.

This is because the OnPush strategy will invoke the change detection routine for a component when:

  • A new value is provided to an Input of the component
  • An event handler inside the component is triggered
  • An Observable used in the component template with | async emits a new value

When should I use the default (CheckAlways) strategy?

If you’re changing state within your component via anything other than the three OnPush triggers mentioned above, or if you mutate the properties of state objects in-place instead of replacing them with new objects, you will need to use the default strategy.

However, a well-architected application with proper separation of concerns through smart/dumb components (more on that in the future), making liberal use of Observables and the async pipe, and having strictly immutable state objects, should seldom need to resort to the relatively expensive default strategy.

So why enable a feature you don’t need?

By using the OnPush strategy as your default, you will be saving your users many wasted clock cycles, battery life, carbon emissions…

Discuss

Comments or questions? Join the conversation on Twitter: