loading...
Cover image for Angular Ivy: a detailed introduction

Angular Ivy: a detailed introduction

eugeniolentini profile image Eugenio Lentini Updated on ・14 min read

Table Of Contents

Angular Ivy is the new rendering architecture that comes, by default, with version Angular 9. The Angular rendering architecture is not new to a complete revamp, Angular 2.0, Angular 4.0 and now Angular 9.0 have introduced new compilers and runtime engines.

Currently, Angular stable version is 8.2.14 and Angular 9 is in RC5.

Disclaimer
The post contains the thoughts of a preliminary investigation on how Angular works reading some parts of the source code, debugging a simple application and reading how the compiler works. Some terms or definition could be wrong.

Slides

This post comes along with a presentation written in markdown, rendered via reveal.js and available on GitHub.

Lingo

  • Rendering architecture: compiler and runtime engine pipeline that allows an Angular application to be executed.
  • Runtime rendering function set/instruction set: set of JavaScript functions understandable by runtime, templates, and decorators are transformed into a sequence of instructions.
  • Virtual DOM and incremental DOM: techniques to create and update a component in the DOM.
  • View Engine: rendering architecture introduced in Angular 4,
  • angular.json is the workspace or build configuration file.
  • tsconfig.app.json is the project configuration file.
  • .ngfactory.js suffix for decorator factory files, class decorators like @Component is translated by the compiler into external files.
  • Locality: compiler should use only information from the component decorator and its class.
  • Global compilation: compilation process requires global static analysis to emit the application code.

Rendering Architecture

What is rendering architecture? It is the pair compiler:runtime. Angular framework is composed of two main parts:

  • compiler to transform templates written in Angular declarative syntax into JavaScript instructions enriched with change detection;
  • runtime to execute the application code produced by the compiler.

Currently, Angular 8 uses as rendering architecture called View Engine:

  • View Engine has been introduced with Angular version 4 and still used in version 8, but some limitations have been identified
    • no tree-shakable: both the Hello World application and a very complex one are executed by the same and full runtime. If the internationalization module is not used, for instance, it is part of the runtime anyhow, basically the runtime it cannot be tree-shakable;
    • no incremental compilation: Angular compilation is global and it involves not only the application but also the libraries.
  • Ivy will the new default rendering engine starting from version 9 and should solve the View Engine current issues:
    • simplify how Angular works internally;
    • tree-shakable the Hello World application does not require the full Angular runtime and will be bundled in only 4.7 KB;
    • incremental compilation is not possible so the compilation is faster than ever and --aot can be now used even during development mode (advice from Angular team).

Ivy is an enabler.

Igor Minar - Angular team

The incremental DOM is the foundation of the new rendering engine.

Incremental DOM vs. Virtual DOM

Every component gets compiled into a series of instructions. These instructions create DOM trees and update them in-place when the data changes.

Viktor Savkin - Nrwl

Each compiled component has two main sets of instructions:

  • view creation instructions executed when the component is rendered for the first time;
  • change detection instructions to update the DOM when the component changes.

Change detection is basically a set of instructions added at compile time. The developer's life is made easier since he is aware only of the variable binding in the Angular template declarative.

Incremental DOM enables better bundle size and memory footprint so that applications can perform really well on mobile devices.

Virtual DOM

Both React and Vue are based on the concept of Virtual DOM to create a component and re-render it when change detection happens.

Render the DOM is a very expensive operation when a component is added to the DOM or changes happen, the repaint operation has to take place. Virtual DOM strategy aims to reduce the amount of work on the real DOM and so the number of times the user interface needs to be repainted.

Tip
The end-user sometimes does not realize the complexity behind the rendering of a user interface. A simple click can generate HTTP requests, changes in the component, changes in other components and so on. Single change for the user can be a complex set of changes that must be applied to the DOM.

DOM manipulations happens every time a new component is going to be added, removed or changed from the DOM, so instead of operating directly on the DOM it operates on a JSON object called Virtual DOM. When a new component is added or an existing one removed, a new Virtual DOM is created, the node added or removed and the difference between Virtual DOMs is computed. A sequence of transformations is applied to the real DOM.

Alt Text

React documentation advice is to use JSX, a syntax extension to JavaScript, to define React elements. JSX is not a template language. A template is an enriched JavaScript expression that is interpreted at runtime. Plain JavaScript can also be used instead of JSX.

Virtual DOM technique has some disadvantages:

  • create a whole tree every time a change happens (add or remove a node), so the memory footprint is quite important;
  • an interpreter is required as long as the diff algorithm to compute the difference among the Virtual DOMs. At compile time it is not known which functionalities are required to render the application, so the whole thing has to be shipped to the browser.

Incremental DOM

It is the foundation of the new rendering engine. Each component template gets compiled into creation and change detection instructions: one to add the component to the DOM and the other one to update the DOM in-place.

Instructions are not interpreted by the Angular runtime, the rendering engine, but the instructions are the rendering engine.

Viktor Savkin - Nrwl

Since the runtime does not interpret the template component instructions, but the component references instructions it is quite easy to remove those instructions that are not referenced. At compile time the unused instruction can be excluded from the bundle.

The amount of memory required to render the DOM is proportional to the size of the component.

Tip
The compiled template component references some instructions of the Angular runtime which holds the implementation.

Enable Angular Ivy

Ivy can be enabled in an existing project with the latest Angular version but also directly scaffold a project with Ivy.

Enable Ivy in an existing project

Having an existing Angular (8.1.x) project run:

$ ng update @angular/cli@next @angular/core@next

both the Angular core and the CLI will be updated at the latest release candidate. One interesting thing to notice is the "aot": true in the angular.json workspace configuration file:

AOT compilation with Ivy is faster and should be used by default.

Angular docs

Then add the angular compiler options in the tsconfig.app.json:

{
  "compilerOptions": { ... },
  "angularCompilerOptions": {
    "enableIvy": true
  }
}

New project with Ivy

To start a new project with Ivy run:

$ new my-app --enable-ivy

Disable Ivy

To disable Ivy:

  • in angular.json set "aot": false;
  • in tsconfig.app.json remove the angularCompilerOptions option or set "enableIvy": false.

Angular Ivy Compilation

Consider the following Hello World Angular component:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <div style="text-align:center">
      <h1>
        Welcome to {{ title }}!
      </h1>
    </div>
  `,
  styleUrls: []
})
export class AppComponent {
  @Input() title = 'Angular!';
}

in Angular 8 with Ivy enabled, it gets compiled into the following code:

class AppComponent {
  constructor() {
    this.title = 'Angular!';
  }
}
AppComponent.ngComponentDef = defineComponent({
        selectors: [['app-root']],
        factory: function() { return new AppComponent();}
    },
    template: function(flags, context) {
        if (flags & 1) {
            elementStart(0, "div", 0);
            elementStart(1, "h1");
            text(2);
            elementEnd();
            elementEnd();
        } if (flags & 2) {...}
    },
    directives: [...]
  });

In Angular 8 with Ivy, the Angular decorators were compiled into static fields in the decorated class. So @Component becomes ngComponentDef static field. Back to View Engine, the ngc compiler produces .ngfactory separated files for each component and modules. With Ivy the code produced by the compiler is moving into component class static fields.

The elementStart(), elementEnd(), etc are the component referenced instructions, every component is its own factory, the framework does not interpret the component.

All the not referenced instructions at compile time are removed from the final application bundle.

Alt Text

Tip
The View Engine runtime is a single monolith interpreter that is not tree-shakable and has to be entirely shipped to the browser. Differently, Angular Ivy runtime is an instruction set that is a set of rendering functions like an assembly language for templates.

In Angular 9 RC5 and Ivy instead the compilation is a bit different:

export class AppComponent {
    constructor() {
        this.title = 'Angular';
    }
}
AppComponent.ɵfac = function AppComponent_Factory(t) { return new (t || AppComponent)(); };
AppComponent.ɵcmp = i0.ɵɵdefineComponent({ type: AppComponent, selectors: [["app-root"]], 
  inputs: { title: "title" }, decls: 3, vars: 1, 
  consts: [[2, "text-align", "center"]], 
  template: function AppComponent_Template(rf, ctx) { 
    if (rf & 1) {
        i0.ɵɵelementStart(0, "div", 0);
        i0.ɵɵelementStart(1, "h1");
        i0.ɵɵtext(2);
        i0.ɵɵelementEnd();
        i0.ɵɵelementEnd();
    } if (rf & 2) {
        i0.ɵɵadvance(2);
        i0.ɵɵtextInterpolate1(" Welcome to ", ctx.title, "! ");
    } }, encapsulation: 2 });

What Angular Ivy enables

Angular Ivy is an enabler. Simplifying how Angular works internally and the compilation process resolves current View Engine limitations and makes Angular easily extensible to new features.

The new Ivy engineering has been driven by three main goals: tree-shaking, locality, and flexibility.

Tree-shaking

Tree-shaking is the operation of removing dead code from the bundle so if the application does not reference some of the runtime rendering function, they can be omitted from the bundle making it smaller.

Dead code comes from libraries, Angular included. Angular CLI is powered by Webpack uglify plugin Webpack Terser plugin as tree-shaker that, in turn, receives information from Angular Build Optimizer Plugin about which code is used and which not. The Angular compiler simply does not emit those instructions, the plugin can gather information about component referenced instructions so can instruct Uglify Terser about what to include/exclude in/from the bundle.

While the @angular/core framework is tree-shakable, the View Engine runtime is not, it cannot be broken into small pieces and this is mainly due to the static Map<Component, ComponentFactory> variable.

Incremental compilation

The Angular 8 compilation pipeline started by ng build prod --aot is composed of five phases where the tsc and the ngc generates the template factories. ngc compiles the libraries as well. Ivy enables Incremental compilation that is libraries can be compiled and deployed on npm.

Locality

Currently Angular relies on global compilation. The compilation process requires a global static analysis of the entire application to combine different compilation outputs (application, libraries from the monorepo and libraries from npm) before emitting the bundle. Moreover, it is really complex to combine AOT libraries into a JIT application.

Tip
The compiler should use only information provided by component decorator and its class and nothing else. This simplifies the overall compilation process, no more component.metadata.json and component.ngfactory.json that requires complex management in the compilation pipeline.

Locality is a rule. Ivy compilation introduces the concept of component/directive public API: an Angular application can safely refer to components and directives public API, no more needed to know much about dependencies since extra information are added to .d.ts component files.

Example: Ivy library compilation

Add a library to the monorepo where your application is running ng generate library mylib.

Compile the library with ng build mylib, the following files are produced:

├── bundles
├── ...
├── lib
│   ├── mylib.component.d.ts
│   ├── mylib.module.d.ts
│   └── mylib.service.d.ts
├── mylib.d.ts
├── package.json
└── public-api.d.ts

Notice as well that this new message is displayed in version 9 due to Ivy activation:

Building Angular Package
******************************************************************************
It is not recommended to publish Ivy libraries to NPM repositories.
Read more here: https://next.angular.io/guide/ivy#maintaining-library-compatibility
******************************************************************************
Generated component

This is the component generated by the Angular CLI:

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'lib-mylib',
  template: `
    <p>mylib works!</p>
  `,
  styles: []
})
export class MylibComponent implements OnInit {

  constructor() { }

  ngOnInit() { }
}
Compiled library code

The metadata file mylib.metadata.json is not generated anymore, metadata are now part of the definition files.

Definition file of the component:

import { OnInit } from "@angular/core";
import * as i0 from "@angular/core";
export declare class MylibComponent implements OnInit {
  constructor();
  ngOnInit(): void;
  static ɵfac: i0.ɵɵFactoryDef<MylibComponent>;
  static ɵcmp: i0.ɵɵComponentDefWithMeta<MylibComponent,"lib-mylib",never,{},{},never>;
}

Definition file of the module:

import * as i0 from "@angular/core";
import * as i1 from "./mylib.component";
export declare class MylibModule {
    static ɵmod: i0.ɵɵNgModuleDefWithMeta<MylibModule, [typeof i1.MylibComponent], never, [typeof i1.MylibComponent]>;
    static ɵinj: i0.ɵɵInjectorDef<MylibModule>;
}

and the definition file of the service:

import * as i0 from "@angular/core";
export declare class MylibService {
    constructor();
    static ɵfac: i0.ɵɵFactoryDef<MylibService>;
    static ɵprov: i0.ɵɵInjectableDef<MylibService>;
}
Add a property to the component

Add to the library component an input field:

@Component({
  selector: 'lib-mylib',
  template: `
    <p>Please input your phone</p>
    <input #phone placeholder="phone number" />
  `,
  styles: []
})
export class MylibComponent implements OnInit {

  @Input('phone-number') private phone: string;

  constructor() { }

  ngOnInit() {
  }
}

The alias phone-number will be added to the input property providing additional metadata for the public API. The compiler generates the following definition file:

import { OnInit } from '@angular/core';
import * as i0 from "@angular/core";
export declare class MylibComponent implements OnInit {
    private phone;
    constructor();
    ngOnInit(): void;
    static ɵfac: i0.ɵɵFactoryDef<MylibComponent>;
    static ɵcmp: i0.ɵɵComponentDefWithMeta<MylibComponent, "lib-mylib", never, { 'phone': "phone-number" }, {}, never>;
}

Decorator that marks a class field as an input property and supplies configuration metadata. The input property is bound to a DOM property in the template. During change detection, Angular automatically updates the data property with the DOM property's value.

Input decorator - Angular docs

The property phone-number is the name part of the public API while phone is the private name, an implementation detail. Since it can change, the code must be compiled every time to emit, in case, an error if there is a property name mismatch. For this reason, the current Angular version must rely on global compilation.

Angular Ivy instead relies on the public API, so library code can be compiled and safely shipped to npm.

Browser property

The input property is bound to a DOM property in the template.

Basically

When the browser loads the page, it “reads” (another word: “parses”) the HTML and generates DOM objects from it. For element nodes, most standard HTML attributes automatically become properties of DOM objects.

Attributes and properties - javascript.info

The Angular compiler transforms the decorators and the templates into JavaScript instructions not only to create elements into the DOM but also extra content properties and attributes used by the runtime to "keep-alive" the application.

Alt Text

Flexibility

Angular Ivy is more flexible than View Engine because if new features are introduced in Angular new instructions will be implemented in the set. Ivy is easier to be extended and optimized.

Angular Ivy Build Pipeline

The compilation of an Angular application is just half of the process since the libraries the application depends on having to be made compatible with the new runtime.

ngcc (Angular compatibility compiler) is a new compiler that convert and compile the libraries. Libraries compatible with ViewEngine, the previous rendering engine of Angular, are converted into Ivy instructions so that the "library can participate in the Ivy runtime" and be fully compatible.

The new compiler has been implemented to make libraries compatible with the new format without obliging maintainers to rewrite important parts of them and, moreover, not all the applications need to be compatible with Ivy.

In Angular version 9 Ivy is enabled for application only and ngcc is used to convert existing libraries making them Ivy compatible. Over time application will start to become more and more Ivy compatible and so the libraries, then ngcc will not be any more necessary. Libraries can be converted on the fly into Ivy compatible libraries during the build or installation process.

The incremental transition from version 9 to version 11 will make ngcc only required for some few cases:

Angular version ngcc
9 app on Ivy (opt-out) and libraries VE compatible
10 stabilize Ivy instruction set, libraries ship Ivy code
11 ngcc backup for obsolete libraries or not updated yet

ngcc-validation project is the way the Angular team tests the libraries' compatibility.

Component lazy loading feature

Angular is an enabler, it will allow more improvement about performance not only for the build but also for the application. Since version 2 Angular has a component lazy loading feature but just at router level. Lazy loading at component level requires a lot of boilerplate code and some patches to make it work.

With Angular Ivy will be much simpler. Consider the following example: click on an image, lazy load the bundle and add the component to the view. Lazy loading improves the speed of an application. Ideally it will be:

@Component(...)
export class AppComponent{
  constructor(
      private viewContainer: ViewContainer,
      private cfr: ComponentFactoryResolver) {

    // lazy click handler
    async lazyload() {
      // use the dynamic import
      const {LazyComponent} = await import('./lazy/lazy.component');
      this.viewContainer.createComponent(LazyComponent);
    }
  }
}

View Engine obliges to pass via the ComponentFactoryResolver to resolve the lazy component into a factory and to load it:

this.viewContainer.createComponent(this.cfr.resolveComponentFactory(LazyComponent));

Bundle size

To evaluate the bundle size improvement, the Angular team uses a metric{: .italic-red-text} the Hello World application. Building with Angular Ivy, the final minimized bundle is ~4.5kB and ~2.7kB with Closure Compiler.

Angular Elements can be then bundled more efficiently and, moreover, Ivy is ready for future bundlers/optimizers.

Debugging

A new API has been added to the global ng object. In the ChromeDevTools just open the console and type ng to see the new options:

Alt Text

Consider to have a <mat-drover></mat-drover> component from the Angular Material library, it is possible to directly act on the component from the console (thanks to Juri Strumpflohner for the example in his tutorial):

// grab the component instance of the DOM element stored in $0
let matDrawer = ng.getComponent($0);

// interact with the component's API
matDrawer.toggle();

// trigger change detection on the component
ng.markDirty(matDrawer);

From the Elements tab just select the element of the debug action, a $0 will appear close to it, it can be used as selector/placeholder for the element in the console.

Alt Text

NgProbe will not be probably supported anymore:

In Ivy, we don't support NgProbe because we have our own set of testing utilities with more robust functionality.

We shouldn't bring in NgProbe because it prevents DebugNode and friends from tree-shaking properly.

Platform browser - Angular source code

Conclusions

Angular team has done an amazing job, it was really a pleasure to attend the Angular Connect 2019 and see the improvement done on the new rendering architecture introduced last year.

Development can be done now with aot compilation enabled by default to avoid possible mismatches between the development and the production environment.

Another interesting point is the Angular Elements. I think the project can now really speeds up thanks to the new compiler and rendering engine. Currently, it is not possible to create a library project and compiler it as web components, this will really a killing feature. Moreover, the generated web components have "too much Angular inside", they are a bit too big, Ivy should reduce the amount of the framework that wraps an Angular component.

Really impressive is the lazy loading that could be achieved in a very simple manner, powerful but keeping the readability of the code simple.

Special thanks

Special thanks to

layzee image

for the peer review and for having found some inaccuracies between Angular 8 and Angular 9 with Ivy enabled.

References

Discussion

pic
Editor guide
Collapse
layzee profile image
Lars Gyrup Brink Nielsen

I've noticed that in Angular 9 with Ivy enabled, the component definition is put in the static property ɵcmp rather than ngComponentDef.

This is also apparent in some of your compiled code examples.

Collapse
eugeniolentini profile image
Eugenio Lentini Author

Indeed, I have noticed as well, but during the Angular Connect 2019 in different cases that code has been called as compiled/generated.
I am waiting some feedback (if they will arrive) from Angular team members, but I guess that the clean and clear code is taken from some intermediate compilation steps and what we see if the final one emitted by the tsc.

I have checked before writing the post and the presentation and often they refer to that code (text(), etc) as compiled, generated, emitted by the Angular compiler.

I should try to see what happens with the ngc alone.

I keep you posted and then I update the post.
Thanks a lot for your reviewing work, appreciated :-)

Collapse
layzee profile image
Lars Gyrup Brink Nielsen

ngComponentDef was in Angular version 8 with Ivy enabled.

Thread Thread
eugeniolentini profile image
Eugenio Lentini Author

I have taken from the slides of the AC 2019, youtube.com/watch?v=S0o-4yc2n-8&li... min 2:00 on going

Thread Thread
layzee profile image
Lars Gyrup Brink Nielsen

This was presented 3 months ago. I first noticed it in RC0, but it might have changed before that.

Slides might say one thing, but code doesn't lie 😬

I have been experimenting a lot with Ivy and View Engine in different versions lately.

Thread Thread
eugeniolentini profile image
Eugenio Lentini Author

Totally agree that the code does not lie, I have considered what they have presented, I will add a small note to the post so people are aware that stuff are moving.

Once 9.0.0 will be out I will check write another post, but for the time being too afraid to see changes from one day to another.

Thanks a lot again for your support, it was I was looking for :-)

Thread Thread
layzee profile image
Lars Gyrup Brink Nielsen

Yeah, I will also be keeping an eye out. I'm thinking of creating a library for Angular library authors to detect Ivy vs. View Engine, production vs. development mode, testing vs. running app.

Thread Thread
eugeniolentini profile image
Eugenio Lentini Author

It would be awesome 👍

Thread Thread
eugeniolentini profile image
Eugenio Lentini Author

Hello,
in the meantime I have fixed the Uglify stuff and added the code compiled with the RC5 :-)

Thread Thread
layzee profile image
Lars Gyrup Brink Nielsen

RC6 now. (Will Ivy be a 2019 release?)

Thread Thread
eugeniolentini profile image
Eugenio Lentini Author

Well unless they sped up for the New Year’s Eve otherwise 2020 😊

Collapse
layzee profile image
Lars Gyrup Brink Nielsen

New finding: The ngComponentDef/ɵcmp key can be determined by importing ɵNG_COMP_DEF from @angular/core. So in version 8, ɵNG_COMP_DEF would evaluate to 'ngComponentDef' whereas it would evalute to 'ɵcmp' in Angular version 9.

There are similar key constants for directives, pipes, and Angular modules.

Collapse
eugeniolentini profile image
Eugenio Lentini Author

Really nice hint! I will have a look, I am working a post about compiler pipeline.

Collapse
layzee profile image
Lars Gyrup Brink Nielsen

Oh dear, I just discovered that this export was called ɵNG_COMPONENT_DEF in Angular version 8, but is renamed to ɵNG_COMP_DEF in Angular version 9 😔

Collapse
layzee profile image
Lars Gyrup Brink Nielsen

The Angular CLI uses Terser for minification, not Uglify.

Collapse
eugeniolentini profile image
Eugenio Lentini Author

I have to double check because during the Angular Connect 2018 they talked about Uglify plugin, may be they have changed the implmentation, thanks, much appreciated :-)

Collapse
layzee profile image
Lars Gyrup Brink Nielsen

Terser is listed as a dependency in Angular CLI's package.json and used in Angular builder.

In the past, they used uglify-es, but it became abandoned and a fork turned into Terser.

Collapse
layzee profile image
Lars Gyrup Brink Nielsen

Angular 9 is currently in RC5*
Angular stable is version 8.2.14.

Collapse
eugeniolentini profile image
Eugenio Lentini Author

Thanks, fixed it!

Collapse
layzee profile image
Lars Gyrup Brink Nielsen

Added note on the latest stable version of Angular. I think you got it confused with Angular CLI which is at 8.3.x.

Thread Thread
eugeniolentini profile image
Eugenio Lentini Author

Fixed that already with the previous.

Thread Thread
layzee profile image
Lars Gyrup Brink Nielsen

8.2.14, not 8.2.9 🙂

Collapse
xchanin profile image
Shannon Bruns

How can libraries be converted on the fly into Ivy compatible libraries during the build or installation process.

Is this something we can do now or is it something that will be available later?

Collapse
eugeniolentini profile image
Eugenio Lentini Author

Hello,
sorry for the delay, I am currently writing a post more focus on the compiler so I will investigate a bit better on the latest RC since things change quite quickly.

For the time being, citing the Compiler Model document on GitHub

ngcc can also be run as part of a code loader (e.g. for Webpack) to transpile packages being read from node_modules on-demand.

Collapse
xchanin profile image
Shannon Bruns

Sounds good. Thanks for the reply.

Collapse
angelnikolov profile image
Angel Nikolov

Hi Eugenio and thanks for the nice article!
I have a quick question though. Do you know if there's a way to use angular material precompiled in Angular 9? Right now ngcc recompiles all libraries on every build, which takes quite a bit of time, while I find it hard to believe there isn't a way to not do that for an official library which @angular/material is.
Any ideas?

Collapse
mrnoctv profile image
loctv

The new Ivy engineering has been driven by three main goals: three-shaking, locality and flexibility.

It should be tree-shaking, right? :)))

Anyway, great article, saves me a lot of time.

Collapse
eugeniolentini profile image
Eugenio Lentini Author

Yep, you are right, fixed the typo, thanks :-)

Collapse
rakiabensassi profile image
Rakia Ben Sassi

Great work Eugenio, thank you for sharing!