Angular Change Detection (Angular Diffing) in Simple Words
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.
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:
- An @Input variable is updated
- A Browser event is invoked (click, change, keyup, etc…)
- setTimeout and setInterval are triggered
- async calls: XHR and promises are executed
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:
- Reference comparison between @Input variables.
- Browser events (click, change, keyup, etc…)
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
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:
As we are going to see Angular uses two strategies to force re-render:
- detectChanges() and markForCheck() methods from ChangeDectorRef base class
- 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.
- detectChanges() checks the view and its children.
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.
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.
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.