📜 ⬆️ ⬇️

Angular 6 and Ivy rendering engine

image Good afternoon, colleagues. We are considering whether to update the book " Angular and TypeScript. Website Building for Professionals " by Jacob Fine and Anton Moiseyev. The new edition is coming out this fall and includes material on Angular 5 and 6.

At first we thought to publish material about the Ivy engine, which will probably be the most interesting innovation in Angular 6, but then we stopped on a more overview publication from Cedric Exbrayat (the original was released in May).

In Angular 6, there are quite a few serious innovations, and the most important of them cannot be called a feature: this is Ivy, a new rendering engine. Since the engine is still experimental, we'll talk about it at the end of this article, and start with other new features and revolutionary changes.

Tree-shakeable providers
')
Now there is a new, recommended way of registering the provider, directly in the decorator @Injectable() , using the new attribute providedIn . It takes 'root' as the value of any module of your application. When using 'root' object being injected will be registered in the application as a loner, and you will not need to add it to the providers in the root module. Similarly, when using providedIn: UsersModule object being providedIn: UsersModule is registered as the UsersModule provider, and is not added to the module providers.

 @Injectable({ providedIn: 'root' }) export class UserService { } 

This new method was introduced for better removal of non-functional code in the application (tree-shaking). Currently, the situation is such that the service added to the providers of the module will be in the final set, even if it is not used in the application - and it’s a little sad to admit. If you use a lazy load, you can fall into several traps at once, or find yourself in a situation where the service will be listed in the wrong set.

Such a situation in applications is unlikely to happen often (if you write a service, then use it), but in third-party modules sometimes we offer services that we don’t need - as a result we have a whole heap of useless JavaScript.

So, this feature will be especially useful for library developers, but now it is recommended to register injected objects in this way - this also applies to application developers. The new CLI now even applies the default providedIn: 'root' scaffolding when working with services.

In the same vein, you can now declare the InjectionToken , directly register it with providedIn and add the factory here:

 export const baseUrl = new InjectionToken<string>('baseUrl', { providedIn: 'root', factory: () => 'http://localhost:8080/' }); 

Note: this also simplifies unit testing. For the purposes of such testing, we are accustomed to register the service with the test module providers. Here’s how we did before:

 beforeEach(() => TestBed.configureTestingModule({ providers: [UserService] })); 

Now, if the UserService uses providedIn: 'root' :

 beforeEach(() => TestBed.configureTestingModule({})); 

Just do not worry: all services registered with providedIn are not loaded into the test, but are lazily instantiated, only in cases when they are really needed.

RxJS 6

Angular 6 now uses RxJS 6 internally, so you will need to update the application to reflect this.

And ... RxJS 6 changes the approach to import!

In RxJS 5, you could write:

 import { Observable } from 'rxjs/Observable'; import 'rxjs/add/observable/of'; import 'rxjs/add/operator/map'; const squares$: Observable<number> = Observable.of(1, 2) .map(n => n * n); 

Pipeable operators appeared in RxJS 5.5:

 import { Observable } from 'rxjs/Observable'; import { of } from 'rxjs/observable/of'; import { map } from 'rxjs/operators'; const squares$: Observable<number> = of(1, 2).pipe( map(n => n * n) ); 

And in RxJS 6.0, imports have changed:

 import { Observable, of } from 'rxjs'; import { map } from 'rxjs/operators'; const squares$: Observable<number> = of(1, 2).pipe( map(n => n * n) ); 

So, one day you will have to change the imports within the entire application. I am writing “once,” not “right now,” because rxjs-compat library was released in RxJS, allowing you to upgrade to RxJS version 6.0, even if the old versions are still used in your entire application or in one of the libraries used syntax.

The Angular team wrote a whole document on this topic, and it is absolutely necessary to read it before migrating to Angular 6.0.

Notice: there is a very cool tslint ruleset called rxjs-tslint . There are only 4 rules in it, and if you add them to the project, the system will automatically migrate all your imports and the RxJS code, and this is done with the simplest tslint --fix ! After all, if you still do not know, tslint has a fix option that automatically fixes all errors it finds! It can be used even more simply: install rxjs-tslint and run rxjs-5-to-6-migrate -p src/tsconfig.app.json . I tried rxjs-tslint on one of our projects, and it worked quite well (run it at least twice to also minimize all imports). Check out the README of this project if you want to learn more: github.com/ReactiveX/rxjs-tslint .

If you are interested in further studying RxJS 6.0, I recommend the following Ben Lesch report on ng-conf.

i18n

The most important i18n perspective is the ability to do “i18n at runtime” without having to build an application separately for each local point. So far this feature is unavailable (there are only prototypes), and its operation will require the Ivy engine (more about it below).

Another i18n change has already taken place and is available. The currency channel has been optimized in the most efficient way: it now rounds all currencies not up to 2 characters, as before, but to the required number of characters (for example, up to 3 in the case of a Bahraini dinar or to 0 in the Chilean peso).

If required, this value can be retrieved programmatically using the new i18n getNumberOfCurrencyDigits function.

In general, other convenient formatting functions also appeared, such as formatDate , formatCurrency , formatPercent and formatNumber .

It is quite convenient if you want to apply the same transformations that are done in the channels, but do it from the TypeScript code.

Animations

In Angular 6.0, animations are already possible without a web-animations-js polyfill, unless you are using AnimationBuilder . Your application can win several precious bytes! In case the browser does not support the element.animate API, Angular 6.0 rolls back to the use of CSS keyframes.

Angular Elements

Angular Elements is a project that allows wrapping Angular components in the form of web components and embedding them in an application that does not use Angular. At first, this project existed only in “Angular Laboratory” (that is, it is still experimental). With the release of v6, it comes to the fore a little bit and is officially included in the framework. This is a big topic that deserves a separate article.

ElementRef <T>

If you want to take a link to an element in your template, you can use @ViewChild or @ViewChildren , or even embed an ElementRef directly. The disadvantage in this case is this: in Angular 5.0 or lower, the specified ElementRef will receive the type any for the nativeElement property.

In Angular 6.0, you can type ElementRef more strictly if you wish:

 @ViewChild('loginInput') loginInput: ElementRef<HTMLInputElement>; ngAfterViewInit() { // nativeElement  `HTMLInputElement` this.loginInput.nativeElement.focus(); } 

What is considered undesirable, and what is fundamentally changing

Let's talk about what you need to keep in mind when embarking on migration!

preserveWhitespaces : default is false

In the “troubles that may occur during the update” section, we note that preserveWhitespaces is now false by default. This option appeared in Angular 4.4, and if you are wondering what to expect at the same time - here is a whole post on this topic. Spoiler: everything can do, and can completely break your templates.

ngModel and reactive forms

Previously, it was possible to provide the same form field with both ngModel and formControl , but today this practice is considered undesirable and will no longer be supported in Angular 7.0.

There is a little confusion, and the whole mechanism worked, perhaps, not as you expected ( ngModel - this was not a familiar directive for you, but the input / output of the formControl directive, which performs almost the same but not identical task).

So now, if we apply the code:

 <input [(ngModel)]="user.name" [formControl]="nameCtrl"> 

then we get a warning.

You can configure the application so that it gives a warning always (always), once (once) or never (never). The default is always .

 imports: [ ReactiveFormsModule.withConfig({ warnOnNgModelWithFormControl: 'never' }); ] 

Anyway, in preparation for the transition to Angular 7, you need to adapt the code to use either template-oriented forms or reactive forms.

Project Ivy: new (new) rendering engine in Angular

Soooo ... This is the 4th major release of Angular (2, 4, 5, 6), and the rendering engine is being rewritten for the 3rd time!

Remember: Angular compiles your templates into equivalent TypeScript code. Then this TypeScript is compiled along with the TypeScript that you have written in JavaScript, and the result is available to the user. And we already have the 3rd version of this rendering engine in Angular (the first was in the original release of Angular 2.0, and the second was in Angular 4.0).

In this new version of the rendering engine, the approach to writing templates does not change, however, it optimizes a number of indicators, in particular:


All this is still deeply experimental, and the new Ivy rendering engine is enabled by the check box, which you yourself must enter in the compiler options (in the tsconfig.json file) if you want to try.

 "angularCompilerOptions": { "enableIvy": true } 

Consider that this mechanism is probably not very reliable, so do not use it in production yet. Perhaps he still does not earn. But in the near future, it will be accepted as the default option, so you should try it once, see if it works in your application, and what you gain from it.

Let's discuss in more detail how Ivy differs from the older rendering engine.

The code generated by the old engine

Consider a small example: let us have a PonyComponent component that accepts a PonyModel model (with name and color parameters) as input and displays a pony image (depending on the suit), as well as a pony name.

Looks like that:

 @Component({ selector: 'ns-pony', template: `<div> <ns-image [src]="getPonyImageUrl()"></ns-image> <div></div> </div>` }) export class PonyComponent { @Input() ponyModel: PonyModel; getPonyImageUrl() { return `images/${this.ponyModel.color}.png`; } } 

The rendering engine, which appeared in Angular 4, generated for each template a class called ngfactory . The class usually contained (code simplified):

 export function View_PonyComponent_0() { return viewDef(0, [ elementDef(0, 0, null, null, 4, "div"), elementDef(1, 0, null, null, 1, "ns-image", View_ImageComponent_0), directiveDef(2, 49152, null, 0, i2.ImageComponent, { src: [0, "src"] }), elementDef(3, 0, null, null, 1, "div"), elementDef(4, null, ["", ""]) ], function (check, view) { var component = view.component; var currVal_0 = component.getPonyImageUrl(); check(view, 2, 0, currVal_0); }, function (check, view) { var component = view.component; var currVal_1 = component.ponyModel.name; check(view, 4, 0, currVal_1); }); } 

It is difficult to read, but the main parts of this code are described as:


Ivy generated code

If we work with Angular 6, and the enableIvy flag enableIvy set to true , then in the same example a separate ngfactory will not be generated; information will be embedded directly into the static field of the component itself (simplified code):

 export class PonyComponent { static ngComponentDef = defineComponent({ type: PonyComponent, selector: [['ns-pony']], factory: () => new PonyComponent(), template: (renderFlag, component) { if (renderFlag & RenderFlags.Create) { elementStart(0, 'figure'); elementStart(1, 'ns-image'); elementEnd(); elementStart(2, 'div'); text(3); elementEnd(); elementEnd(); } if (renderFlag & RenderFlags.Update) { property(1, 'src', component.getPonyImageUrl()); text(3, interpolate('', component.ponyModel.name, '')); } }, inputs: { ponyModel: 'ponyModel' }, directives: () => [ImageComponent]; }); // ...   } 

Now everything is contained in this static field. The template attribute contains the equivalent of the usual ngfactory , with a slightly different structure. The template function, as before, will be launched for any change, but now it has two modes:


What does it change?

Now all decorators are embedded directly in their classes (the same for @Injectable , @Pipe , @Directive ), and to generate them you only need to know about the current decorator. In Angular, this phenomenon is called the “locality principle”: you do not need to re-analyze the application to recompile a component.

The generated code is slightly reduced, but more importantly, it is possible to eliminate a number of dependencies, thus speeding up recompilation if one of the parts of the application changes. In addition, with modern collectors, for example, Webpack, everything turns out much nicer: the non-functional code is reliably cut off, those parts of the framework that you do not use. For example, if you have no channels in the application, then the framework needed to interpret them is not even included in the final set.

We are used to the Angular code that gets heavy. It happens, it is not scary, but Hello World weighing 37 kb after minification and compression is too much. When Ivy is responsible for generating the code, the non-functional code is cut off much more efficiently. Now Hello World after minification is compressed to 7.3 kb, and after compression - just up to 2.7 kb, and this is a big difference. The TodoMVC application after compression is only 12.2 kb. This is data from the Angular team, and the others could not work with us, since in order for Ivy to work as described here, you still need to patch it manually.

If you are interested in details, watch this performance with ng-conf.

Compatible with existing libraries

You might be interested: what will happen to libraries that are already published in the old format, if you use Ivy in your project? Do not worry: the engine will make an Ivy-compatible version of the dependencies of your project, even if they were compiled without Ivy. I won't tell you the interior right now, but all the details should be transparent.

New opportunities

Consider what new features we will have when working with this display engine.

Private properties in templates

New engine adds new feature or potential change.
This situation is directly related to the fact that the template function is embedded in the static field of the component: now we can use the private properties of our components in the templates. Previously, this was impossible, which is why we were forced to keep public all fields and component methods used in the template, and they fell into a separate class ( ngfactory ). When accessing a private property from another class, TypeScript compilation would fail. Now it is in the past: since the template function is in a static field, it opens access to the private properties of the component.

I saw a comment from the members of the Angular team about the fact that it is not recommended to use private properties in templates, although this is now possible - since it may be again prohibited in the future ... therefore, it is probably wiser to use only public fields in templates! In any case, writing unit tests is now easier, because the test can check the status of a component, even without generating or checking for this DOM.

i18n at runtime

Pay attention: the new rendering engine finally opens up a long-awaited opportunity for us and gives “i18n during execution”. At the time of this writing, she was still not quite ready, but we saw several commits at once, and this is a good sign!
The great thing is that you don’t have to pretty much change your application if you are already working with i18n. But now you no longer need to build the application again for each locale that you plan to support - it will be enough just to load JSON with translations for each locale, and Angular will take care of the rest!

Libraries with AoT code

At the moment, a library released in NPM must publish the file metadata.json and cannot publish the AoT code of its components. This is sad because the costs associated with such an assembly are passed on to our application. With Ivy, there is no need for a metadata file, and library authors will now be able to publish their AoT code directly to NPM!

Improved Statistics

Now the generated code should give improved patterns, if you have a problem with your templates - result in a neat error indicating the template line in which it occurred. You can even set breakpoints in templates and track what is actually happening in Angular.

NgModule disappear?

This is still a distant prospect, but perhaps in the future it will be possible to do without NgModules. The first signs of such changes are tree-shakeable providers, and it is logical to assume that Ivy has all the necessary basic blocks for those who are ready to gradually abandon NgModules (or at least make them less successful). True, all this is still in perspective, we will be patient.

In this release there will not be many new features, but Ivy is definitely interesting for the future. Experiment with it - I wonder how you like it!

Source: https://habr.com/ru/post/419389/


All Articles