changeDetection
We know that the decorator functions of @Component take object and this object contains many properties. Then we will learn about the changedetection properties in this article.
Change Detection means updating the DOM every time the data is changed.
When modifying any of the models, Angular detects the changes and updates the views immediately. This is the detection of changes in Angular. The purpose of this mechanism is to ensure that the underlying views are always synchronized with their corresponding models.
A model in Angular can change as a result of one of the following scenarios:
- DOM events (click, hover over, etc.)
- AJAX requests
- Timers (setTimer(), setInterval())
Change Detectors
All angular applications consist of a hierarchical tree of components. At runtime, Angular creates a change detector class separately for each tree component, which then eventually forms a hierarchy of change detectors similar to the hierarchy tree components.
When change tracking is activated, Angular goes through this tree of change detectors to determine if any of them have changed.
The change detection cycle is always performed once for each detected change and starts from the root change detector and is sequentially reduced. This sequential design option is useful because it updates the model in a predictable way because we know that component data can only come from its parent.
The change detectors provide a way to keep track of the previous and current states of the component, as well as its structure in order to report changes to Angular.
If Angular gets the report from a change detector, it tells the corresponding component to re-render and update the DOM accordingly.
Angular provides two strategies for Change Detection.
default strategy
In your default strategy, every time you put or edit any data, Angular will run the change detector to update the DOM.
In other words, as we stated before, Angular monitors the changes in the model to make sure it captures all the changes. It will verify any difference between the previous state and the current state of the general application model.
To know if the view must be updated, Angular must access the new value, compare it with the previous one and decide if the view must be updated.
By default, Angular makes no assumptions about what the component depends on. Therefore, it must be prudent and will check every time something has changed, this is called dirty checking. In a more concrete way, it will perform checks for every browser event, timer, XHR and promises.
This can be problematic when you start having a big application with many components, especially if you have focused on performance.
By default, Angular must be cautious and will check every time something has changed, this is called dirty checking.
onPush strategy
When using the onPush strategy in the component, in practice, you tell Angular that you should not speculate about when to perform check for change.
It will be based only on the modification of the input references, some events activated by themselves (the component) or one of his children. Finally, you, the developer, can explicitly ask Angular to do it with the componentRef.markForCheck () method.
With onPush, the component depends only on its inputs and covers immutability, the change detection strategy will be activated when:
- The input reference changes;
- An event originating from the member or one of his children;
- Execute change detection explicitly (componentRef.markForCheck ());
- Use the async pipe in the view.
In the onPush strategy, Angular only performs the change detector when a new reference to the data of @Input () is passed.
To update the DOM with up-to-date data, Angular provides its own change detector to each component responsible for tracking changes and updating the DOM.
Let's say we have a MessageComponent, as listed below:
message.component.ts
import { Component, OnInit, Input } from '@angular/core'; @Component({ selector: 'app-message', template: ` <h2> Hey {{company.firstname}} {{company.lastname}} ! </h2> ` }) export class MessageComponent implements OnInit { @Input() company; constructor() { } ngOnInit() { } }
In addition, we are using MessageComponent within AppComponent as shown below:
app.component.ts
import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-root', template: ` <app-message [company]='varmessage'></app-message> <button (click)='changeName()'>Change Name</button> ` }) export class AppComponent implements OnInit { title = 'app'; varmessage: any; ngOnInit(): void { this.varmessage = { firstname: 'Sahosoft', lastname: 'Tutorials' }; } }
Let's examine the code: all we are doing is using MessageComponent as a child within AppComponent and setting the value of the person using the property binding.
At this point, when you run the application, you will get the name printed as output.
Next, let's go ahead and update the property of the first name on the button, click on the AppComponent class below:
changeName() { this.varmessage.firstname = 'Sahosoft Angular'; }
As soon as we change the property of the mutable object P, Angular activates the change detector to make sure that the DOM (or view) is synchronized with the model (in this case, the object p). For each change of property, the angular change detector will traverse the component tree and update the DOM.
We begin by understanding the component tree. An application of angular can be seen as a component tree. Start with a root component and then switch to the secondary components. In Angular, the data flows from top to bottom in the component tree.
Whenever the @Input type property is changed, the angular change detector will start from the root component and traverse all the child components to update the DOM. Any change in the primitive type property will cause changes to be detected to detect the change and update the DOM.
In the code snippet above, you'll find that when you click the button, the name in model will be changed. Then, change detection will be activated to move from root to end to update the view in MessageComponent.
There may be several reasons why the angular change detector goes into action and begins to travel the component tree. They are:
- fired events such as button clicks, etc.
- AJAX calls or XHR requests.
- Use of the JavaScript timer functions like setTimeOut, SetInterval.
Now, as you can see, a single change of property can cause the change detector to pass through the entire component tree. Moving and detecting changes is a heavy process, which can cause degradation of application performance.
Imagine that there are thousands of components in the tree and that the mutation of any data property can cause the change detector to go through the thousand components to update the DOM. To avoid this, there may be a scenario where you want to tell Angular that when the change detector needs to be run for a component and its subtree, you can tell the detector that a component changes to be performed only when the object references they change in place of mutation of any property. Choose the onPushChangeDetection strategy.
It is recommended that you indicate to Angular that you are tracking changes to components and their subtree only when new references to that data are passed, rather than when data is limited to change when you configure the change detection strategy on onPush.
Let's go back to our example where we pass an object to MessageComponent. In the last example, we just changed the property of the first name and this causes the change detector to run and update the MessageComponent view.
However, we now want the change detector to run only when the object reference passed is changed rather than just a property value. To do this, we modify MessageComponent to use the OnPush ChangeDetection strategy. To do so, set the changeDetection property of the @Component decorator to ChangeDetectionStrategy.OnPush as shown in the following list:
message.component.ts
import { Component, OnInit, Input , ChangeDetectionStrategy } from '@angular/core'; @Component({ selector: 'app-message', changeDetection: ChangeDetectionStrategy.OnPush, template: ` <h2> Hey {{company.firstname}} {{company.lastname}} ! </h2> ` }) export class MessageComponent implements OnInit { @Input() company; constructor() { } ngOnInit() { } }
At this point, when you run the application, if you click on the AppComponent change detector button, it will not run for MessageComponent, because only one property is changed and the reference does not change.
Because the change tracking strategy is set to onPush, the change detector will only run when the @Input property reference is changed.
changeName() { this.varmessage = { firstname: 'Sahosoft Angular', lastname: 'Tutorials' } }
In the above code snippet, we are changing the object reference rather than simply changing a property. Now, when you run the application, you will find with the button click that the DOM is updated with the new value.
When using OnPush Change Detection, Angular will only check the tree if the reference that is passed to the component is modified instead of changing some properties in the object. We can summarize this and use an immutable object with onPush change detection to improve performance and perform the change detector for the component structure when the object reference is changed.
We can further improve performance by using Observable RxJS because they emit new values without changing the object reference. We can subscribe to the observable to get a new value and then run ChangeDetector manually.
Edit AppComponent to pass an observable to MessageComponent.
app.component.ts
import { Component, OnInit } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; @Component({ selector: 'app-root', template: ` <app-message [company]='data.value'></app-message> <button (click)='changeName()'>Change Name</button> ` }) export class AppComponent implements OnInit { title = 'app'; varmessage: any; data: any; ngOnInit(): void { this.varmessage = { firstname: 'Sahosoft', lastname: 'Tutorials' }; this.data = new BehaviorSubject(this.varmessage); } changeName() { this.varmessage = { firstname: 'Sahosoft Angular', lastname: 'Tutorials' } this.data.next(this.varmessage); } }
In the code, we are using BehaviorSubject to emit the next value as observable for MessageComponent. We imported BehaviorSubject from RxJS and wrapped an object p to create an observable object. In the button click event, you get the next value in the observable sequence and pass to MessageComponent.
In MessageComponent, we must subscribe to the person who is reading the data.
export class MessageComponent implements OnInit { @Input() company: Observable; _data; constructor() { } ngOnInit() { this.company.subscribe(data => { this._data = data; }); } }
Now, with the click of the button, a new value is created, however, a new reference is not created because the object is an observable object. Because no new reference is created due to onPush ChangeStrategy, Angular does not detect changes. In this scenario, to update the DOM with the new observable value, we need to manually call the change detector as shown below:
export class MessageComponent implements OnInit { @Input() company: Observable; _data; constructor(private cd: ChangeDetectorRef) { } ngOnInit() { this.company.subscribe(data => { this._data = data; this.cd.markForCheck(); }); } }
We have imported the ChangeDetectorRef service and injected it, so we call markForCheck () manually to make the change detector run every time observable emits a new value. Now, when you run the application and click on the button, the observable will emit a new value and the change detector will also update the DOM, even if a new reference is not created.
To be remember
- If the Angular ChangeDetector is configured by default, for any change in any model properties, Angular will track changes through the component structure to update the DOM.
- If the Angular ChangeDetetor is set to onPush, Angular only runs the change detector when a new reference to the component is passed.
- If you pass observable to the onPush change detector's enabled component, Angular ChangeDetctor must be called manually to update the DOM.