Angular Change Detection (Angular Diffing) in Simple Words

David
The Startup
Published in
5 min readJan 12, 2021
Photo by Andreas Gücklhorn on Unsplash

Most of the time we don’t need to care about change detection until we need to optimize the performance of our application. If we don’t use it correctly the performance can decrease especially in larger applications.

Obviously, if we consider it from the beginning we’ll save ourselves a lot of headaches, and as you are going to see, the extra effort in Angular is minimal.

In this article, I pretend to give you a well-explained description of what Change Detection is, why is it so important, and how to handle it in Angular.

Diffing

It means… we can’t apply changes to the DOM without comparing the old with the new version of it.

Most frameworks use a copy of the actual DOM to optimize for incoming updates. Any changes to the component layer are first applied to the copy and then compared to the actual DOM, making sure to only change the updated fraction instead of the entire DOM.

Angular uses a similar strategy.

1. The green component is updated in a copy 2. A comparison is done between the actual DOM and its copy 3. The real DOM branch is re-rendered

Angular Change Detection Strategies

It’s the diffing implementation in Angular, it will check all the components from top to bottom in the component tree, and if there was a change in a component view a portion of the DOM will be re-rendered.

Default Change Detection

Change detection in the Default Change Detection Strategy is executed when:

  1. An @Input variable is updated
  2. A Browser event is invoked (click, change, keyup, etc…)
  3. setTimeout and setInterval are triggered
  4. async calls: XHR and promises are executed
Checks every branch from top to bottom: 1. An action happens in the component with the green arrow and affects the green component 2. Checks happen from top to bottom on component tree 3. Branch is re-rendered

It should be noticed that in the Default Change Detection, ChildComponents are re-rendered even when the change on ParentComponent has no impact on the view of ChildComponent. Let’s see it within an example:

Now, imagine a big application with thousands of components; If we let Angular check every one of them when a change detection cycle runs, we might encounter a performance problem. It’s a kind of brutal force comparison, for that reason, this technique is called dirty checking.

OnPush Change Detection

Change detection in the OnPush Change Detection Strategy is executed through:

  1. Reference comparison between @Input variables.
  2. Browser events (click, change, keyup, etc…)
1. An action happens in the component with the green arrow and affects the green component 2. Checks happen only in the updated branch 3. The branch is re-rendered. Note: components rounded in blue means are using OnPush Strategy

This strategy prevents unnecessary checks for a ChildComponent and its children when a it is independent from its Parent (@Input variables don’t change by reference). See figure above.

We set this strategy in our component by adding: changeDetection: ChangeDetectionStrategy.OnPush in the Component decorator

Reference https://netbasal.com/a-comprehensive-guide-to-angular-onpush-change-detection-strategy-5bac493074a4

Forcing Change Detection in OnPush strategy

Sometimes force diffing in OnPush strategy could be necessary, for example when we use data services to share state instead of Input variables or Output events in cascade:

Note it doesn’t work, so would be necessary to force in some way the render

As we are going to see Angular uses two strategies to force re-render:

  1. detectChanges() and markForCheck() methods from ChangeDectorRef base class
  2. async pipes

ChangeDectorRef

It’s a class that can be injected into the constructor’s component and provides change detection functionality as detectChanges and markForCheck methods.

  1. detectChanges() checks the view and its children.
1. detectChanges() method is called in the green component 2. Comparison is been made starting only by that component 3. re-render the DOM

2. markForCheck() doesn’t trigger change detection. Instead, it marks all OnPush ancestors to be checked once, either as part of the current or next change detection cycle.

1. We change the state of the green component and then we call the markForCheck() on one of its children 2. Every component up is marked to be checked 3. OnPush comparison is done on the next cycle
Open the console and see what happens when you are using detectChanges(), then comment it and uncomment markForChek(), what’s the difference?

Async Pipe

It subscribes to an observable and returns the latest emitted value; when the component is destroyed… automatically unsubscribes, and (here is the thing 🪄) internally calls markForCheck() when a new value is emitted.

Calling an HTTP Request with an async pipe inside a component with OnPush Strategy

Conclusion

In the Default Change Detection Strategy, the diffing runs from any action from the action list, and by value comparison between @Input variables.

In the OnPush Change Detection Strategy, the diffing runs only by browser events, triggered by the component or its children, and reference comparison between @Input variables.

Sometimes force Change Detection within the OnPush strategy could be necessary, in which cases you can use most of the time two methods from ChangeDetectorRef base class:

detectChanges() which checks the view and its children.

markForCheck() which marks all OnPush ancestors to be checked once, either as part of the current or next change detection cycle.

async pipes which internally calls markForCheck each time a new value is emitted.

Reference

--

--

David
The Startup

Hi and welcome, I write about programming in an easy and friendly way so everyone get it