How does Change Detection mechanism work in Angular today?

Philipp Plotnikov
Nerd For Tech
Published in
5 min readJul 22, 2021
Photo by Laura Ockel on Unsplash

Most Angular developers think that change detection(in the future I will name it as CD) is the object, but it is not true. When our app is started up Angular passes the two-component initialization phases:

  • constructing components tree
  • running change detection

The components tree is the incremental tree whose nodes are objects of class View. Angular creates a View for every component. The class View object plays the role of a container that contains all needed information and logic to look for the app state and reflects those changes on our screens. The Change detection mechanism is part of every View object.

View State

Each view has a state, which plays a very important role because based on its value CD decides whether to run change detection for the view and all its children or skip it. The view state can have the following values(you do not need to remember them as they can be changed from version to version):

  1. FirstCheck
  2. ChecksEnabled
  3. Errored
  4. Destroyed

Change detection skips the view and its child views if ChecksEnabled is false or the view is in the Errored or Destroyed state. By default, all views are initialized with ChecksEnabled unless ChangeDetectionStrategy.OnPush is used. The states can be combined, for example, a view can have both Errored and Destroyed flags set. CD is triggered with such events:

  1. MicroTasks (for example Promise)
  2. MacroTasks (for example setTimeout)
  3. Browser events(for example click)

Thanks to the zone.js library, it is achieved that patches the browser environment to track all the above-mentioned events. NgZone(Angular wrapper of zone.js) uses it to invoke the CD mechanism automatically. We would call the CD manually without it. Unfortunately, most developers think that the Input value changing invokes CD too. It is not true.

Change detection logic

As I mentioned above there is a method in every View object that contains the logic of CD. The name of that method and the step sequence of this logic are irrelevant as they are changed from version to version. Our target is to understand the main idea.

  1. Checks ViewState.ChecksEnabled (if it is false, the CD does not process this object and its descendants)
  2. Sets ViewState.FirstState to true if a view is checked for the first time and to false if it was already checked before
  3. Checks and updates input properties on a child component/directive instance
  4. Updates child view change detection state
  5. Runs CD for the embedded views (repeats the steps in the list)
  6. Calls OnChanges lifecycle hook on a child component if bindings changed
  7. Calls OnInit and ngDoCheck on a child component (OnInit is called only during the first check)
  8. Updates ContentChildren query list on a child view component instance
  9. Calls AfterContentInit and AfterContentChecked lifecycle hooks on child component instance (AfterContentInit is called only during the first check)
  10. Updates DOM interpolations for the current view if properties are changed in the current view(component instance)
  11. Runs CD for a child view (repeats the steps in this list)
  12. Updates ViewChildren query list on the current view component instance
  13. Calls AfterViewInit and AfterViewChecked lifecycle hooks on child component instance (AfterViewInit is called only during the first check)
  14. Disables checks for the current view

There are few things that I wanted to highlight.

Firstly, I want to draw your attention to the step under the number 4. The CD sets ViewState.ChecksEnabled in the child View only when its input properties are changed otherwise it does nothing.

Secondly, we can mention that step under the number 6 says that ngOnChanges() can be called even the ViewState.CheckesEnabled is set to false

Thirdly, DOM updating(step under the number 10) happens element by element. Before updating every element to which some class property is bound Angular compare the current value of this property with the value that is stored in property oldValues. If these values are different Angular updates this element(values are compared by reference).

And finally, CD disables check for the current view(the step under the number 14) only if we use ChangeDetectionStrategy.OnPush. This is one difference between the two strategies ChangeDetectionStrategy.Default and ChangeDetectionStartegy.OnPush. Exactly because of this reason Default strategy goes through the whole tree every time it is triggered but OnPush is not.

Development mode

In development mode, Angular runs CD twice to check if the value has changed since the first run. In production mode change detection is only run once to have a better performance. To avoid it we have to understand that the second CD call is placed inside the function that contains the CD logic so there is no way we can interrupt the second loop to update the old values. To avoid the error ExpressionChangedAfterCheckedError we can:

  1. Recursively call CD in the second loop to update property oldValues.(for example detectChanges())
  2. Instructions that CD has to execute to get current value to make as Micro or MacroTask. In these situations, the second loop doesn’t get a new value so it uses a value that was stored in a variable at the last loop what we need
  3. Change the application logic so that such a situation does not exist

ChangeDetectionRef methods

Let’s assume that we have the following components tree:

As we learnt above, each component is associated with a component view. Each view is initialized with the ViewState.ChecksEnabled which means when angular runs change detection every component in the tree will be checked.

Suppose we want to disable change detection for the AComponent and its children. That’s easy to do, we just need to set ViewState.ChecksEnabled to false. Angular provides use ChangeDetectionRef object as an interface with which we can solve our task.

class ChangeDetectorRef {
markForCheck (): void
detach (): void
reattach (): void

detectChanges (): void
checkNoChanges (): void
}

Let’s how we can use it.

Method detach()

This method simply disables checks for the current view, set ViewState.ChecksEnabled to false. I want to mention that children of that View will not be checked too.

Method reattach()

This method simply enables checks for the current view, set ViewState.ChecksEnabled to true.

Method markForCheck()

The reattach method enables checks for the current component only, but if changed detection is not enabled for its parent component, it will not affect. It means that reattach method is only useful for a top-most component in the disabled branch. Method markForCheck() enable check for all parent components up to root component.

Method detectChanges()

This method runs change detection once for the current component and all its children. This method runs change detection for the current component view regardless of its state, which means that checks may remain disabled for the current view and a component will not be checked during following regular change detection runs

Method checkNoChanges()

This last method ensures that the values are not changed(it checks ViewState.ChecksEnabled). Throws an exception if it finds a changed binding or determines that DOM should be updated.

Thanks for your attention. I hope this article will help you to understand the Change Detection mechanism in Angular.

--

--