What are we going to learn?

Optimization of performance is a critical aspect of the development of your Angular applications.

An effective way to increase your app’s performance is to reduce constant UI re-rendering, which will boost the speed and performance of your app.

Change detection is the process of updating/re-rendering the DOM to reflect the new state of the data model. Different JS frameworks have different ways it implements its change detection. AngularJS uses Scope , $scope.apply() and $scope.digest() , React.js uses setState() , and Angular uses NgZone/Zone.js to detect when to trigger change detection cycle.

Aside from triggering change detection cycle, change detection can be triggered from and for a component. In this article, we learn how to run a local change detection, change detection meant to only re-render the calling component’s view and its child’s’ views. Let’s dive in.

Tip: Use Bit to share and reuse your components between apps. Discover and play with components, and use them to build new apps faster. Give it a try.

UI Spinners with Bit: Choose, play, use

View and Change Detector tree

A View in Angular is the smallest grouping of elements created from a component.

@Component({

selector: `app-root`,

template: `

<div>

App works!!!

</div>

`

})

export class AppComponent {}

This represents a single View in a tree of Views in an Angular application.

In the angular world a View is a fundamental building block of the application UI. It is the smallest grouping of elements which are created and destroyed together. — Max NgWizard K ViewRef Represents an Angular View. A View is a fundamental building block of the application UI. It is the smallest grouping of Elements which are created and destroyed together. — Angular Github (core/src/linker/view_ref.ts)

To describe the Angular tree of views, let’s look at this example.

@Component({

selector: `child2`,

template: `

<div>

App child2 works!!!

</div>

`

})

export class Child2Component {} @Component({

selector: `child1`,

template: `

<div>

App child1 works!!!

<child2></child2>

</div>

`

})

export class Child1Component {} @Component({

selector: `app-root`,

template: `

<div>

App works!!!

<child1></child1>

</div>

`

})

export class AppComponent {}

We have three components: AppComponent, Child1Component and Child2Component. AppComponent has child1 tag in its view, so on rendering the view of Child1Component appended inside the child1 tag. Looking at Child1Component view, it has child2 in its view, so the view of Child2Component is rendered inside the child2.

<app-root> 1.

<div>

App works!!!

<child1> 2.

<div>

App child1 works!!!

<child2> 3.

<div>

App child2 works!!!

</div>

</child2> 3.

</div>

</child1> 2.

</div>

</app-root> 1.

That’s how it will appear in your browser. Do you see a tree of views?

app-root (1.) is the root of the tree. child1 (2.) is a branch of app-root and child2 (3.) is the branch of child1(2.)

app-root

|

child1

|

child2

Angular applications are composed of views with a tree-like hierarchy. You have the main view (app-root), and then you have subviews, and so forth.

A view is directly related to a component. The component manipulates the view through data-binding and async operation. A reference to the view’s component instance is stored in the view’s component property.

[app-root]

|

----------------

| |

[child1] [child2]

| |

----------------- ----------

| | | | |

[a_ch] [b_ch] [c_ch] [d_ch] [e_ch]

Each of these views has its own change detector for updating view and its child’s’ view when its properties change.

When change detection is run, the app-root view is first checked then proceeds to check its siblings using the depth-first algorithm.

To know more about how Angular processes the change detector tree, I’d recommend He who thinks change detection is depth-first and he who thinks it’s breadth-first are both usually right by Max NgWizard K

change detection for child2 would only run d_ch and e_ch, because they are children of child2. a_ch, b_ch, and c_ch won’t be run because they are not children of child2. Likewise, when child1 change detection is run, only its children’s change detection will be triggered.

We have seen in this section, that an Angular app can be represented in a tree of views. Since the views have their own change detector, it can be seen as a tree of change detectors. In the next section, we will see how to run a local change detection from a component.

ChangeDetectorRef class and Local change detection

We just saw that views are in a tree-like structure, and that as each of the views have their own change detector, change detection in Angular can also be represented in a tree-hierarchy.

When Angular runs change detection using the tick method, it starts from the top of the tree, and goes down to the bottom.

Let’s suppose we want to disable change detection for child1 view/component, so that change detection from the top of the tree won’t update its view.

To make that happen, we need to inject ChangeDectorRef class into the component’s constructor. To demonstrate the use of ChangeDetectorRef class for a local change, let’s look at the example below:

@Component({

selector: 'web-xhr',

template: `

<div>{{data}}</div>

`

})

export class WebXhr {

data:any

constructor() {

setInterval(()=>{

// getDataFromServer is a hypothetical method. It gets info from a server and assigns it to the `data` property.

this.data = getDataFromServer()

},4000)

}

}

The component above gets data from the server at a 4-second interval and assigns it to the data property. The setInterval async function triggers a change detection which updates all the views in the Angular app.

The issue here is that change detection is run every 4 seconds, which affects our app because the UI might become unresponsive which leads to a bad user experience.

Angular has a bunch of high-level concepts to manipulate the views. I’ve written about some of them here. One such concept is ViewRef. It encapsulates the underlying component view and has an aptly named method detectChanges. When an asynchronous event takes place, Angular triggers change detection on its top-most ViewRef, which after running change detection for itself runs change detection for its child views. — Max NgWizard K

To prevent that, we have to turn off the component’s change detection.

To do that, we have to get a handle on the component’s ChangeDetectorRef instance and call detach method, which as its name implies, detaches the component's change detector from the tree.

Now, we can call the component's change detection manually. Let’s edit our WebXhr to manually call its change detection:

export class WebXhr {

data:any

constructor(private chngRef: ChangeDetectorRef) {

this.chngRef.detach()

setInterval(()=>{

// `getDataFromServer` is a hypothetical method. It gets info from a server and assigns it to the `data` property.

this.data = getDataFromServer()

this.chngRef.detectChanges()

}, 4000)

}

}

We injected ChangeDetectorRef instance, then called the detach method. We called it in the constructor so that it removes its change detector from the tree during instantiation. Next, we called the detectChanges method after updating the data, so it triggers the component's change detection.

So, at the interval of 4 seconds, the component will run a UI update on itself and its corresponding child views.

ChangeDectorRef: A deeper look

Let’s look at the implementation of the ChangeDetectorRef class

export abstract class ChangeDetectorRef {

abstract markForCheck(): void; abstract detach(): void; abstract detectChanges(): void; abstract checkNoChanges(): void; abstract reattach(): void;

}

markForCheck: This method marks all ancestors (with OnPush change detection strategy) of a view to being checked.

Let’s look at the implementation:

markForCheck(): void { markParentViewsForCheck(this._view); }

...

export function markParentViewsForCheck(view: ViewData) {

let currView: ViewData|null = view;

while (currView) {

if (currView.def.flags & ViewFlags.OnPush) {

currView.state |= ViewState.ChecksEnabled;

}

currView = currView.viewContainerParent || currView.parent;

}

}

This iterates through the current view parent, setting their state to ChecksEnabled .

detach: This method detaches the current view from the change detector tree.

detach(): void { this._view.state &= ~ViewState.Attached; }

Every view is created with the state set to CatInit :

function createView(

root: RootData, renderer: Renderer2, parent: ViewData | null, parentNodeDef: NodeDef | null,

def: ViewDefinition): ViewData {

...

const view: ViewData = {

...

➥ state: ViewState.CatInit

};

return view;

}

Also, looking the ViewState flags at types.ts:

/**

* Bitmask of states

*/

export const enum ViewState {

BeforeFirstCheck = 1 << 0,

FirstCheck = 1 << 1,

Attached = 1 << 2,

ChecksEnabled = 1 << 3,

IsProjectedView = 1 << 4,

CheckProjectedView = 1 << 5,

CheckProjectedViews = 1 << 6,

Destroyed = 1 << 7, // InitState Uses 3 bits

InitState_Mask = 7 << 8,

InitState_BeforeInit = 0 << 8,

InitState_CallingOnInit = 1 << 8,

InitState_CallingAfterContentInit = 2 << 8,

InitState_CallingAfterViewInit = 3 << 8,

InitState_AfterInit = 4 << 8, CatDetectChanges = Attached | ChecksEnabled,

➥ CatInit = BeforeFirstCheck | CatDetectChanges | InitState_BeforeInit

}

We see here that, CatInit bitmask also sets the Attached and ChecksEnabled flags which Angular checks during change detection cycle. Attached indicates the view is still attached to the tree and ChecksEnabled indicates the current view should be checked for updates.

So during change detection by the checkAndUpdateView function, it checks the state of the current to know how to proceed:

...

const viewState = view.state;

...

if ((viewState & ViewState.Destroyed) === 0) {

if ((viewState & ViewState.CatDetectChanges) === ViewState.CatDetectChanges) {

checkAndUpdateView(view);

} else if (viewState & ViewState.CheckProjectedViews) {

execProjectedViewsAction(view, ViewAction.CheckAndUpdateProjectedViews);

}

}

You see that it assigns the state value to a viewState variable. It then runs the checkAndUpdateView function on the view, if the CatDetectChanges flag is set in the view.

Remember that our view, when created, is set to CatInit which enable CatDetectChanges.

So, the detach method ands the CatInit bitmask which destroys the CatDetectChanges state so the view is skipped. Let's demonstrate this in a Node environment.

const ViewState = {

BeforeFirstCheck: 1 << 0,

FirstCheck: 1 << 1,

Attached: 1 << 2,

ChecksEnabled: 1 << 3,

Destroyed: 1 << 7, // InitState Uses 3 bits

InitState_BeforeInit: 0 << 8, CatDetectChanges: (1 << 2) | (1 << 3),

CatInit: (1 << 0) | ((1 << 2) | (1 << 3)) | (0 << 8)

} // our view with state property set to CatInit

const view = {

state: ViewState.CatInit

}

const viewState = view.state if ((viewState & ViewState.Destroyed) === 0) {

if ((viewState & ViewState.CatDetectChanges) === ViewState.CatDetectChanges) {

console.log('checkAndUpdateView')

} else if (viewState & ViewState.CheckProjectedViews) {

console.log('execProjectedViewsAction(view, ViewAction.CheckAndUpdateProjectedViews);')

}

}

If we run the program, we will see:

$ checkAndUpdateView

Let’s detach the view using the detach method implementation:

...

const view = {

state: ViewState.CatInit

}

// view detached

➥ view.state &= ~ViewState.Attached const viewState = view.state

...

If we run the program again, nothing will be printed on the screen. This is because the CatDetectChanges flag has been destroyed, so UI updates on the view.

Looking at how it is used in components:

export class AComponent {

constructor(private ref: ChangeDetectorRef) {

this.ref.detach()

}

}

This detaches the left branch from the tree. The components in blue will not be updated.

To demonstrate this, let see how the structure of the components will look like in Angular:

const v = {

nodes: [{

element: {

name: 'app-root',

componentView: {

state: 'check',

id: 'app-rootView',

nodes: [{

element: {

name: 'a',

componentView: {

id: 'aView',

state: 'check',

nodes: [{

element: {

name: 'aa'

}

},

{

element: {

name: 'ab'

}

}

]

}

}

},

{

element: {

name: 'b',

componentView: {

id: 'bView',

state: 'check',

nodes: [{

element: {

name: 'ba'

}

},

{

element: {

name: 'bb'

}

}

]

}

}

}

]

}

}

}]

}

The Angular tree of views is actually a linked-list of objects. A child view is referenced by the componentView property.

NB: The above structure is the simplified version of Angular’s ViewData. ViewData has many properties, we only showed properties that will help us understand how change detection works.

The above structure is how Angular represents its views. The componentView refers to a view’s child view. From our picture, you see that app-root has two child views a and b. Looking at the view structure, you see a and b inside the nodes array enclosed by app-root’s componentView property. Likewise aa and ab for a and ba and bb for b.

Also, we assigned an id for each to let know which view is updated during change detection, and there is a state property at each view that indicates whether to run a change detection on a view.

Next, let’s simulate how Angular runs change detection on the view tree.

There is a checkAndUpdateView, which runs execComponentViews then callViewAction.

The root of the view tree is passed to checkAndUpdateView which updates directives and bindings on the view, then calls execComponentViews, this iterates through the view nodes and gets a handle on its componetView property if the node has a child view. It calls the callViewAction function with the componentView, which checks the state property to knwo whether to run change detection on the view.

change detection run is a recursive call through checkAndUpdate function.

Let’s write a simplified version of the functions:

function checkAndUpdateView(view) {

execComponentView(view)

} function execComponentView(view) {

let _nodes = view.nodes

for (var index = 0; index < _nodes.length; index++) {

var __node = _nodes[index].element

if (__node.componentView) {

callViewAction(__node.componentView)

}

}

} function callViewAction(view) {

var viewState = view.state

const { id } = view

if (viewState == 'check') {

console.log(id, ' child views to be updated.')

checkAndUpdateView(view)

}

}

checkAndUpdateView(v)

The important thing here is that we checked for a view state to know if to run UI update on itself and children. If we run this program on Node we will see this displayed:

$ node test

app-rootView child views to be updated.

aView child views to be updated.

bView child views to be updated.

All views are run. If we want to detach a view from the tree, we just set its state to something other than check , like this:

...

componentView: {

id: 'aView',

state: 'check_',

...

This disables the check for a view and its child. Runnig the program agian, we get:

$ node test

app-rootView child views to be updated.

bView child views to be updated.

You see the a view was skipped. So that's what happens when we call detach method from a component it disables check for the component's view.

reattach: This re-connects a detached component to the change detector tree. It is the opposite of detach :

reattach(): void { this._view.state |= ViewState.Attached; }

It flips back the bitmask on the view state property to enable the CatDetectChanges flag.

detectChanges: This runs a change detection on the current view, with the view being the root.

detectChanges(): void {

const fs = this._view.root.rendererFactory;

if (fs.begin) {

fs.begin();

}

try {

Services.checkAndUpdateView(this._view);

} finally {

if (fs.end) {

fs.end();

}

}

}

It calls the checkAndUpdateView function with the current view as param. It parses through the view tree like we demonstrated in the detach section, then updates its UI and its childs' if their state is set to be checked.

checkNoChnages: This method checks that no changes were made on the current run of the view’s change detector. The method will throw if any changes were detected.

checkNoChanges(): void { Services.checkNoChangesView(this._view); }

... export function checkNoChangesView(view: ViewData) {

markProjectedViewsForCheck(view);

Services.updateDirectives(view, CheckType.CheckNoChanges);

execEmbeddedViewsAction(view, ViewAction.CheckNoChanges);

Services.updateRenderer(view, CheckType.CheckNoChanges);

execComponentViewsAction(view, ViewAction.CheckNoChanges);

// Note: We don't check queries for changes as we didn't do this in v2.x.

// TODO(tbosch): investigate if we can enable the check again in v5.x with a nicer error message.

view.state &= ~(ViewState.CheckProjectedViews | ViewState.CheckProjectedView);

}

Conclusion

In this post, we learned how change detection can be triggered locally by a component. Next, we took a deeper look at the ChangeDetectorRef implementation and how its methods work with a little demonstration on the detach method.

I think with this, the vague concept of change detection in Angular will become clearer.

If you have any question regarding this or the little demo on detach method, feel free to comment, email or DM me

Thanks !!!

Resources

To further understand change detection, here are some useful links: