Angular Performance improvement using change detection — Default and OnPush
Change detection is responsible to update the view (DOM) data when state data has changed. By default, when a state value in component is changed, Angular updates all child component to sync the value between view and component for all time. We can take some strategy to avoid the unnecessary component update that will improve the Angular application performance. Most of the time (around 80–90% of projects), you don’t need to work with change detection, but if you work with large Angular application or handle with large data that’s time some change detection strategy make the application robust and fast. In this article, I will try to cover this strategy to improve the Angular performance.
How change detection works:
ngZone is responsible to run the asynchronous code and also automatically inform the Angular to run the change detection operation based on events (Click, Timer, HTTP call) . We are not going to deep about ngZone.
Types of change detection strategy?
By default, Angular updates the each component from top to bottom when any event like browser event, API call, or timer happened in any component. If we can reduce the the number of checking, then we are able to reduce the run change detection update for sub component. There are two type strategy for change detection:
- Default strategy
- OnPush strategy
Default strategy:
By default, Angular uses this strategy ChangeDetectionStrategy.Default
to render view of a component. It will update all component view from top to bottom, whenever an event is triggered( eg. XHR, promise, user events) of any component. This unnecessary change detection checking leads to performance issue when you are working with large project or handling large data.
Here NG code 1, after clicking the “Change title” button, title is changing for the change detection of ParentComponent. At the same time, change detection of ChildComponent and GrandChildComponent update the view render whereas there is no state change (see Output-1).
When default change detection is called:
- Any event (Click, Key-up, Change event, etc)
- HTTP request for API
- Timers (setInterval(), setTimeout())
OnPush strategy
OnPush strategy will not check the unnecessary dirty checking in ChildComponent and it’s child component ,GrandChildComponent, after applying the ChangeDetectionStrategy.onPush
strategy. It will make the component faster. (See NG code 2 and Output 2).
Here, we can see that, title state of ChildComponent is not updating due to “changeDetection: ChangeDetectionStrategy.OnPush”. We can change the state manually by running the change detector to update the view. Angular provides markForCheck method that instruct Angular to check the component as the next time change detectors run. We will discuss later in below sections.
ChangeDetectionStrategy.OnPush
change detection in your component would be run when
- A primitive or reference of an @Input() value has changed
- An event is received
(click, keyup)
| async
pipe received an event- Change detection is invoked “manually”
OnPush and the mutability trap ( An "@Input()
value has changed"
)
In the NG code 3, we can see user object is sending as a “@Input” properties. When application is initialize that time it is showing the the user name. But after clicking the “Change user name” button, user name in object is changing in ParentComponent, but this changes is not effecting the ChildComponent. Because the user object is mutated from the parent element, the reference is not updated in the Child Component, so @Input Component is not receiving a new reference and will not be updated since the Objects in @Input property as still same reference.
So, we should create a new object with updated properties so that the reference of @Input property updates and the ChildComponent is notified about the Component Update. Change the code of changeName() method like as NG code 4, then it we can see the changed user name in ChildComponent.
An event is received in OnPush component:
OnPush component invokes the change detection when any browser event triggered on those component. So, “NG code 3" just replaced by “NG code 5” in ChildComponent. Now the ChildComponent has a Refresh button. A click on the Refresh button would instruct Angular to run the change detector, and, as a result of that, the view will be updated with the latest value of the user.
OnPush, Observables and the async pipe ( Async
pipe received an event):
By using the async pipe, we don’t have to manually call the change detector, subscribe to the observable, and unsubscribe to the observable because the async pipe does all these tasks. See the NG code 6 .
The async pipe is a cleaner approach, and it is recommended to use it while working with observable data and onPush change detection strategy.
Change detection is invoked “manually”:
We can mark a view as dirty using the ChangeDetectionRef
abstract class methods. Use the methods to add and remove views from the CD tree, initiate change-detection, and explicitly mark views as dirty, meaning that they have changed and need to be re-rendered. The methods are
markForCheck()
detach()
detectChanges()
reattach()
markForCheck():
In the OnPush strategy, we can use markForCheck()
method to mark the view as dirty for re-rendering the view by Change Detection.
detach() and detectChanges():
These two methods are used together. detach()
is used to detach the view component from the Change Detection tree, and detectChanges()
is used to call when you want the change detection to run. The detectChanges
method runs the change detector for the current component and its children.
reattach():
reattach
the previously detached view to the change detection tree.
References:
- Simplifying Angular Change Detection
- What is Change Detection in Angular?
- Angular OnPush Change Detection and Component Design — Avoid Common Pitfalls
- Stackoverflow — change detection and ChangeDetectionStrategy.OnPush
- Angular Performances Part 4 — Change detection strategies
- onPush and Default Change Detection Strategy in Angular
- Angular ChangeDetectorRef