While working on my previous article on Document Object Model and its types, I discovered a major shift in the DOM-handling that came with the introduction of Angular Ivy. That brought me to the idea of this post — how is rendering done in Angular?
The Angular rendering architecture — i.e.compiler and runtime engine pipeline that allows an application to be executed — has changed multiple times: with Angular 2.0, Angular 4.0 and now with Angular 9.0. In this article we will look into the last 2 solutions — View Engine and Ivy — to understand how it works and what is new.
[TL;DR: pretty much all UI frameworks nowadays store some representation of the DOM to optimize change detection and to improve re-rendering performance; in Ivy, two steps have been compressed into one so that the DOM representation can re-build itself from instructions, no separate interpretation needed. This is possible due to the Increment DOM]
Let’s start with the problem statement.
The major key to the smooth user interaction is a smart and quick change detection for content update, styling adjustment and layout change. Someone said layout? That it where it starts to hurt. Despite some general belief, DOM operations (e.g. adding or removing some elements) per se are not slow, but layout re-rendering takes a long time. Check out an interesting discussion here.
So, to solve this problem, several strategies have been introduced. React and Vue focused on virtual DOM (not part of this article), Angular went with templating and bindings. As you might anticipate, both solutions have their right to exist, no “ultimate weapon” here.
In this article we will take a closer look at the “Angular way” of handing change detection and rendering. We will start with the previos compiler and runtime engine (View Engine). This will help us understand what pitfalls are addressed by the new one and what is actually new (and if it’s good).
The View Engine, called
Renderer2, was released with Angular 4. Let’s have a look how it renders and handles changes.
- When Angular first processes the application code, it makes a note of all associations between the HTML properties and the TypeScript (TS) properties. Then it goes about creating binding — a set of instructions for each HTML-TS-property association. Since these bindings are component-specific, it is easier to optimize them than some generic instuctions. The resulting template data is then interpreted and transformed into DOM.
The process described above (a.k.a. rendering pipeline) can be illustrated as follows:
Let’s have a look at the following component:
In View Engine, it will be compiled to the following template (node_modules/.bin/ngc). Note all these “i0.ɵeld” — this is what the bindings look like.
2. After bindings (and DOM) have been created, Angular only deals with the bindings for change detection and not with the entire application code.
4. The binding for which a property has changed is marked as updated (or dirty).
If the property value before and after is different, it will set
isChanged to true (line 34), and that's it (simplified)! This is done for all components. Angular does not do deep object comparison to detect changes, it only takes into account properties used by the template.
5. Next, the template data is being interpreted and transformed into DOM with the latest bindings.
This is the very basic understanding of how change detection, rendering and DOM is handled in View Engine. Now, let’s move on and look how Ivy is dealing with these topics.
Ivy is the code name for Angular’s next-generation compilation and rendering pipeline. With the version 9 release of Angular, the new compiler and runtime instructions are used by default instead of the older compiler and runtime, known as View Engine.
Being not only a rendering engine, but also a compiler, Ivy brings along many new topics and optimizations across the whole framework. They are, however, beyond the scope of this article, for it is already long enough. But bear with me, we are going to discuss some cools stuff now.
So, what has changed in a way the change detection and rendering is hanled? Instead of generating template data and passing it into an interpreter that then makes decisions on which operations to run, a set of template instructions is now generated directly. These template instructions is where the logic that instantiates components, creates DOM nodes, and runs change detection lives now.
Here is what the new rendering piple looks like. Compare it with the one above — see 1 step missing? There is no longer need for template interpretation, just execute it to build the DOM. How is it possible? With the help of the Incremental DOM. Let’s have a look at it.
When the DOM knows how to build itself: Incremental DOM
The idea of the Incremental DOM dates back to 2015. It differs from Virtual DOM approaches in that a diff operation is performed incrementally (that is one node at a time) against the DOM representation itself, rather than on a Virtual DOM tree (a copy of the DOM which can differ from “the real one” before reconciliation is finished).
The recipe is: while re-creating the DOM tree walk along the existing tree and figure out changes as you go. Allocate no memory if there is no change; if there is, mutate the existing tree (update value in-place and only allocate memory when DOM nodes have been added/removed) and apply the diff to the physical DOM. As we typically don’t add/remove many elements in our application templates (at least in the majority of SPA cases), this translates into a remarkable memory saving, especially for large projects, as per this post.
To enable in-place mutation, the internal DOM representation should be changed. As you know, DOM can be pictured as a tree with nodes that represent the (html) document structure. Take it, run some rendering algorithm over it and voilá, you’ve got your document. But what if we encode the rendering algorithm in the tree itself OR what if we encode the tree into our rendering algorithm? What if it knew, how to transform itself into the document? This is indeed what happens in the Incremental DOM in Angular. The template function contains the instructions rendering and updating the DOM. Note that the instructions aren’t interpreted by the framework’s rendering engine. They are the rendering engine itself.
What could these incremental node functions look like? Well, we need something for opening and closing tags and the inner html:
text. Remeber our sample component from above? Ivy will compile it to the following:
Check out the following article for real-world compiled components.
An additional positive aspect evolves from it— only the instructions that will be used are going to end up in the final bundle, all the rest is tree-shaked away. It turns Angular Ivy runtime to an instruction set or a set of rendering functions like an assembly language for templates. This is the revolutionary aspect: being able to make the rendering engine itself tree shakable. More detailed explanation can be found here and here. Tree-shaking does not first come with Ivy, though. View Engine (Renderer2) already did some static code analysis. Yet it provided a single monolith interpreter that was not tree-shakable at runtime and had to be entirely shipped to the browser.
Ok, as you can see, the rendering process has now 3 steps instead of 4. How does it impact the change detection? Well, in a sense, the underlying principles stay the same: Ivy will still create bindings/instructions (i0.ɵɵtextBinding(2, i0.ɵɵinterpolation1(“”, ctx.title, “”));) and update values during the change detection cycle. These instructions will directly re-construct the affected node (no interpretation step needed). The specific implementation details will vary for sure, but the overall strategy stays the same: Angular leverages its template knowledge. Let’s re-iterate on it: how does this DOM concept does in comparison with the Virtual DOM?
Incremental DOM vs. Virtual DOM
Here are some considerations:
- It is only logical to leverage additional information that Angular has about its templates. Why compare the whole DOM (doing diffing) if you exactly know at what place things can change and have changed?
- Mobile browsing has largely subsumed user browsing trends. However, web developers cannot influence the hardware of the user’s device. Thus, it totally makes sense to optimize for memory-constrained devices with the Incremental DOM. Ideally, the final bundle itself should be smaller, too.
Here is a nice explanation by Sean Grogg. The Google User Experience Engineer states:
Thus, applications that perform fewer DOM updates will benefit from the reduced memory consumption of Incremental DOM while those that perform a large number of DOM updates will benefit more from Virtual DOM’s greater performance.
In most applications there are often relatively fewer DOM operations actually going on at any given time. As well, performance is often good enough such that the performance wins of Virtual DOM aren’t realized by the average user (often still operating above 60 FPS in Incremental DOM cases).
To sum up
The strength of Angular’s rendering strategies lies in the template knowledge. The framework knows what properties are involved in what template and can target and locate them more quickly if they change. The new rendering engine introduces the concept of the Incremental DOM which helps to get read of the interpretation step in the rendering pipeline. It is a logical step towards leveraging full Angular strength, yet it is just one of possible strategies for rendering optimization.
There many great articles that go in-depth on this topic. Here is a selection of them:
Ivy engine in Angular: first in-depth look at compilation, runtime and change detection
I usually finish my talks with the philosophical phrase that nothing stays the same . And as you probably know it's…
How Angular Ivy works on Incremental DOM
The key idea behind Incremental DOM is every component gets compiled into a series of instructions. These instructions…
Angular Ivy: a detailed introduction
Angular Ivy is the new *rendering architecture* that comes, by default, with version Angular 9. The Angular rendering…
A look at major features in the Angular Ivy version 9 release
🇪🇸 Spanish version by Alberto Basalo In previous versions of Angular, we had to opt-in to Ivy. In version 9, we…
[Disclaimer: you think the author misses the point or provides incorrect information? Blame the author AND provide missing / relevant / correct information in your comments — help other readers (and the author) to get it straight! a.k.a. #learningbysharing]