Overview
In any application, the process of updating the screen takes the internal state of the program and projects
it into something users can see on the screen. In web development, we take data structures like objects and arrays
and end up with a DOM representation of that data in the form of images, buttons and other visual elements.
Frameworks take care of synchronization between the internal state of the application and the underlying platform.
But not only do they take some weight off our shoulders, they do the state tracking and DOM updates very efficiently.
This mechanism of synchronization is called rendering.
It has different names – change detection in Angular or reconciliation in React – but the core operations it performs
are similar across different implementations.
Change detection is one of the most important pieces of an architecture since
it’s responsible for the updates of the platform model (DOM) responsible for visible parts.
It’s also the area that significantly affects an application’s performance.
Going forward I’ll be using the terms rendering and change detection interchangeably.
To define the relationship between UI and application state we use expressions in templates.
In the templates below, we’re saying that the DOM property className
depends on the component’s property rating
.
So whenever the rating changes, the expression should be re-evaluated.
If a change is detected, the className
property should be updated.
<div [className]="'fa-star ' + (rating > value ? 'fas' : 'far')"></div>
In Angular, when the compiler analyzes the template, it identifies properties of a component that are associated with DOM elements.
For each such association, the compiler creates a binding.
A binding defines a relationship between a component’s property (usually wrapped in some expression) and the DOM element property.
The change detection mechanism executes instructions that process bindings.
The job of these instructions is to check if the value of an expression with a component property has changed
and perform DOM updates if necessary.
Processing bindings that perform dirty checks and update the relevant parts of the DOM are the core operations of change detection in Angular.
There are other operations that we’ll take a detailed look at the Operations chapter.
For now let’s take a quick look at how Angular updates DOM.
DOM updates overview
This interactive widget allows a user to click on any star and set the new rating:
That’s how we could implement it:
@Component({
selector: "rating-widget-cmp",
template: `
<ul class="rating">
<li
*ngFor="let value of values"
[className]="'fa-star ' + (rating > value ? 'fas' : 'far')"
(click)="onRatingClick(value)"
>
{{ value }}
</li>
</ul>
`,
})
export class RatingWidgetComponent {
values = [0, 1, 2, 3, 4];
rating = 2;
onRatingClick(v: number) {
this.rating = v + 1;
}
}
The rating
property in the template is bound to the className
property through the expression:
[className]="'fa-star ' + (rating > value ? 'fas' : 'far')"
For this part of the template, the compiler generates instructions that set up a binding, performs dirty checks and update the DOM.
Here’s the code that is generated for our template:
if (changeDetectionPhase) {
property("className", "fa-star " + (ctx.rating > 0 ? "fas" : "far"));
...
}
Here we can see the instruction property
(prefixed with ɵɵ
in the sources) that checks if the value of the expression has changed,
and if so marks the binding as dirty and updates the value.
Angular created the binding for the className
and the current values of the binding is 'fa-star far'
.
Once the rating property in the component is updated, Angular runs change detection and processes the instructions.
The property
instruction in the simplified code looks like this:
export function property(propName, value, ...) {
const lView = getLView();
const bindingIndex = nextBindingIndex();
if (bindingUpdated(lView, bindingIndex, value)) {
const tView = getTView();
const tNode = getSelectedTNode();
elementPropertyInternal(tView, tNode, lView, propName, value, ...);
}
return property;
}
First we get a reference to the LView,
which is a container for Angular components and all relevant data.
Then using nextBindingIndex
we retrieve the index in the LView
that holds information about the binding on the propName
.
In our case it’s the className
property.
The bindingUpdated
function evaluates the expression and uses it to compare to the previous value remembered by the binding.
This is where the name “dirty checking” comes from. If the value has changed, it updates the current value and returns true
:
If the expression changed, Angular runs elementPropertyInternal
function that uses the new value to update the DOM.
In our case it will update the className
property of the list item.
The property
function returns itself so that it may be chained:
property("name", ctx.name)("title", ctx.title);
That’s it. We will take a detailed look at other instructions in the "Inside rendering engine" section.
Triggering change detection
To have a comprehensive understanding of change detection, we need to know when Angular executes instructions that process bindings.
There are two ways to initiate change detection.The first one is to explicitly tell the framework
that something has changed or there’s a possibility of a change so it should run change detection.
Basically, in this way we initiate change detection manually.
The second way is to rely on the external mechanism to know when there’s a possibility
of a change and run change detection automatically.
In Angular we have both options. We can use the Change Detector service to
run change detection manually:
export class RatingWidgetComponent {
values = [0, 1, 2, 3, 4];
rating = 2;
constructor(private changeDetector: ChangeDetectorRef) {}
onRatingClick(v: number) {
this.rating = v + 1;
this.changeDetector.detectChanges();
}
}
However, we can also rely on the framework to trigger change detection automatically.
In this way, we simply update the property on a component:
export class RatingWidgetComponent {
values = [0, 1, 2, 3, 4];
rating = 2;
onRatingClick(v: number) {
this.rating = v + 1;
}
}
Angular somehow needs to know that the property is updated and it should run change detection.
To solve this Angular uses a library called
zone.js.
It patches all asynchronous events in a browser and can then notify Angular when a certain event occurs.
Similarly to UI events, Angular can then wait until the application code has finished executing
and initiate change detection automatically. We’ll explore zone.js
in great detail in the section on zones.
Order of checks
Angular runs change detection for each component in the depth-first order. This means for a component tree depicted below the checks will happen in the following order A, K, L, J, O, C
and so on:
Since a component check includes multiple operations, the order of executing certain steps may produce a bit different results. For example, operations that result in DOM and binding updates are executed in the proper depth-first order. I’m using the function logRender to log exactly when Angular updates the template and executes the function to evaluate the template expression:
@Component({
selector: "a-cmp",
template: `{{ logRender() }}`,
})
export class A {
logRender() {
console.log("A");
}
}
Check the live running example here.
But if I add logging into the ngDoCheck
hook like this:
@Component({
selector: "a-cmp",
template: `A`,
})
export class A {
ngDoCheck() {
console.log("A");
}
}
This is the order you will see:
Angular checks A
, K
and then V
, L
and then C
and so on.
Check the live running example here.
This is neither a depth-first nor a proper breadth-first algorithm.
The conventional implementation of the breadth-first algorithm checks all siblings on the same level,
whereas in the diagram above as you can see the algorithm indeed checks L
and C
sibling components,
but instead of checking X
and F
it goes down to J
and O
.