Angular Ivy change detection execution: are you prepared?

Alexey Zuev
Angular In Depth
Published in
8 min readMay 19, 2018

AngularInDepth is moving away from Medium. More recent articles are hosted on the new platform inDepth.dev. Thanks for being part of indepth movement!

Update:

Try Ivy jit mode

https://alexzuza.github.io/ivy-jit-preview/ 👈

Let’s see what Angular cooks for us

Fan of Angular-In-Depth? Support us on Twitter!

Disclaimer: it is just my learning journey to new Angular renderer

The Evolution of Angular View Engine

While new Ivy renderer is not feature completely yet, many people wonder how it will work and what changes it prepares for us.

In this article I am going to visualize Ivy change detection mechanism, show some things I am really excited about and also build simple app based on instructions, similar to angular Ivy instructions, from scratch.

First, let’s introduce the app I’m going to investigate here:

I created online demo that I use to understand how it works under the hood:

https://alexzuza.github.io/ivy-cd/ 👈

The demo uses angular 6.0.1 aot compiler. You can click on any lifecycle block to go to the definition.

In order to run change detection process just type something in one of those inputs that are below Sub-Child.

View

Of course, the view is the main low-level abstraction in Angular.

For our example we will get something like:

Root view
|
|___ AppComponent view
|
|__ ChildComponent view
|
|_ Embedded view
| |
| |_ SubChildComponent view
|
|_ Embedded view
| |
| |_ SubChildComponent view
|
|_ SubChildComponent view

View should describe template so it contains some data that will reflect structure of that template.

Let’s look at ChildComponent view. It has the following template:

Whereas current view engine creates nodes from view definition factory and stores them in nodes array of view definition

Ivy creates LNodes from instructions, that are written in ngComponentDef.template function, and stores them in data array:

Besides nodes, new view also contains bindings in data array(see data[4], data[5], data[6] in the picture above). All bindings for a given view are stored in the order in which they appear in the template, starting with bindingStartIndex

Note how I get view instance from the ChildComponent. ComponentInstance.__ngHostLNode__ contains reference to the component host node. (Another way is to inject ChangeDetectorRef)

This way angular first creates root view and locate host element at index 0 in data array

RootView
data: [LNode]
native: root component selector

and then goes through all components and fills data array for each view.

Change detection

Well known ChangeDetectorRef is simply abstract class with abstract methods like detectChanges, markForCheck, etc.

When we ask this dependency in component constructor we actually gets ViewRef instance that extends ChangeDetectorRef class.

Now, let’s examine internal methods that are used to run change detection in Ivy. Some of them are available as public api(markViewDirty and detectChanges) but I am unsure about others.

detectChanges

Synchronously performs change detection on a component (and possibly its sub-components).

This function triggers change detection in a synchronous way on a component. There should be very little reason to call this function directly since a preferred way to do change detection is to use markDirty(see below) and wait for the scheduler to call this method at some future point in time. This is because a single user action often results in many components being invalidated and calling change detection on each component synchronously would be inefficient. It is better to wait until all components are marked as dirty and then perform single change detection across all of the components

tick

Used to perform change detection on the whole application.

This is equivalent to `detectChanges`, but invoked on root component. Additionally, `tick` executes lifecycle hooks and conditionally checks components based on their `ChangeDetectionStrategy` and dirtiness.

scheduleTick

Used to schedule change detection on the whole application. Unlike tick, scheduleTick coalesces multiple calls into one change detection run. It is usually called indirectly by calling markDirty when the view needs to be re-rendered.

markViewDirty(markForCheck)

Marks current view and all ancestors dirty.

Whereas early in Angular 5 it only iterated upwards and enabled checks for all parent views, now please note that markForCheck does trigger change detection cycle in Ivy!!! 😮 😮 😮

markDirty

Mark the component as dirty (needing change detection).

Marking a component dirty will schedule a change detection on this component at some point in the future. Marking an already dirty component as dirty is a noop. Only one outstanding change detection can be scheduled per component tree. (Two components bootstrapped with separate `renderComponent` will have separate schedulers)

checkNoChanges

Nothing new:)

When I was debugging new change detection mechanism I noticed that I forgot to install zone.js. And as you have already guessed it worked perfectly without that dependency and without cdRef.detectChanges or tick . Why?

As you probably know by design Angular triggers change detection for onPush component only if (see my answer on stackoverflow).

These rules are also applied to the Ivy:

I have (input) output binding in SubChildComponent. The second rule will result in calling markForCheck. Since we have already learned that this method actually calls change detection it should be clear now how it works without zonejs.

What about Expression has change after it was checked?

Don’t worry, it is still here:)

Change detection order

Since Ivy was announced Angular team has been doing hard work to ensure that the new engine correctly handles all lifecycle hooks in the correct order. That means that the order of operations should be similar.

Max NgWizard K wrote in his great article(strongly suggest reading it):

As you can see, all the familiar operations are still here. But the order of operations appears to have changed. For example, it seems that now Angular first checks the child components and only then the embedded views. Since at the moment there’s no compiler to produce output suitable to test my assumptions, I can’t know for sure.

Let’s come back to ChildComponent in my simple app

It was intended from my side to write one sub-child as regular component before others that are inside embedded view.

Now it’s time to see it in action:

As we can angular first checks embedded view and then regular component. So there is no changes here from previous engine.

Anyway, there is optional “run Angular compiler” button in my demo and we can test other cases.

https://alexzuza.github.io/ivy-cd/

One-time string initialization

Imagine we wrote component that can receive color as string input value. And now we want to pass that input as constant string that will never be changed:

It’s so called one-time string initialization and angular documentation states:

Angular sets it and forgets about it.

As for me, it means that angular won’t do any additional checks for this binding. But what we actually see in angular5 is that it is checked per every change detection cycle during updateDirectives call.

See also great article “Getting to Know the @Attribute Decorator in Angular” about this issue by Netanel Basal

Now let’s look at how it is supposed to be in new engine:

As we can see angular compiler stores our constant outside of the code that is responsible for creating and updating component and only uses this value in create mode.

Angular no longer creates text nodes for containers

Update: https://github.com/angular/angular/pull/24346

Even if you don’t know how angular ViewContainer works under the hood you may noticed the following picture when opening devtools:

In production mode we see only <!—-->.

And here’s the Ivy output:

I can’t be sure 100% but seems we will have such result once Ivy gets stable.

As a result the query in the code below

will return null since angular

should no longer read ElementRef with a native element pointing to comment DOM node from containers

Incremental DOM(IDOM) from scratch

A long time ago Google announced so-called incremental DOM library.

The library focuses on building DOM trees and allowing dynamic updates. It wasn’t intended to be used directly but as a compilation target for template engines. And seems the IVy has something in common with incremental DOM library.

Let’s build simple app from scratch that will help us to understand how IDOM render works. Demo

Our app will have counter and also print user name that we will by typing in input element.

Assume we already have <input> and <button> element on the page:

Existing html page

And all we need to do is to render dynamic html that will look like:

In order to render this let’s write elementOpen, elementClose and text “instructions” (I call it this way because Angular uses such names as IVy can be considered as special kind of virtual CPU).

First we need to write special helpers to traverse nodes tree:

Now, let’s write instructions:

Put differently, these functions just walk through DOM nodes and insert node at current position. Also text instruction sets data property so that we can see text value the browser.

We want our elements to be capable of keeping some state, so let’s introduce NodeData:

Now, let’s change our renderDOM function so that we won’t add new element to the DOM if there is already the same at current position:

Note my comment /*, key */ . It would be better if our elements have some key to distinguish elements. See also http://google.github.io/incremental-dom/#demos/using-keys

After that let’s add logic that will be responsible for text node updates

The same we can do for element nodes.

Then let’s write patch function that will take DOM element, update function and some data that will be consumed by update function:

Finally, let’s test our instructions:

The result can be found here

You can also verify that the code will only update the text node whose contents has changed by inspecting the with the browser tools:

So the main concept of IDOM is to just use the real DOM to diff against new trees.

That’s all. Thanks for reading…

--

--