Table Of Contents
- Slides
- Lingo
- Rendering architecture
- Enable Angular Ivy
- Angular Ivy compilation
- What Angular Ivy enables
- Angular Ivy build pipeline
- Component lazy loading feature
- Bundle size
- Debugging
- Conclusions
- References
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.
-
no tree-shakable: both the
-
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.
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.
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 theangularCompilerOptions
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.
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.
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:
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.
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
for the peer review and for having found some inaccuracies between Angular 8 and Angular 9 with Ivy enabled.
Top comments (29)
I've noticed that in Angular 9 with Ivy enabled, the component definition is put in the static property
ɵcmp
rather thanngComponentDef
.This is also apparent in some of your compiled code examples.
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 :-)
ngComponentDef was in Angular version 8 with Ivy enabled.
I have taken from the slides of the AC 2019, youtube.com/watch?v=S0o-4yc2n-8&li... min 2:00 on going
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.
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 :-)
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.
It would be awesome 👍
Hello,
in the meantime I have fixed the Uglify stuff and added the code compiled with the RC5 :-)
RC6 now. (Will Ivy be a 2019 release?)
Well unless they sped up for the New Year’s Eve otherwise 2020 😊
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.
Really nice hint! I will have a look, I am working a post about compiler pipeline.
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 😔The Angular CLI uses Terser for minification, not Uglify.
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 :-)
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.Angular 9 is currently in RC5*
Angular stable is version 8.2.14.
Thanks, fixed it!
Added note on the latest stable version of Angular. I think you got it confused with Angular CLI which is at 8.3.x.
Fixed that already with the previous.
8.2.14, not 8.2.9 🙂
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?
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
Sounds good. Thanks for the reply.
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?
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.
Yep, you are right, fixed the typo, thanks :-)
Great work Eugenio, thank you for sharing!