Angular 6 and Ivy rendering engine

Original author: Cédric Exbrayat
  • Transfer
imageGood 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 on 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 Exbraat (original ).

In Angular 6, there are quite a few serious innovations, and the most important of them cannot be called a feature: this is Ivy, the 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 a provider, directly in the decorator @Injectable(), using a new attribute providedIn. It takes 'root'as the value of any module of your application. When using an 'root'injected object will be registered in the application as a loner, and you do not need to add it to the providers in the root module. Similarly, when applied, the providedIn: UsersModuleobject being injected is registered as a provider UsersModule, and is not added to the module providers.

@Injectable({
  providedIn: 'root'
})
exportclassUserService{
}

This new method was introduced for better removal of non-functional code in the application (tree-shaking). At present, 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 bunch 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 uses scaffolding by default providedIn: 'root'when working with services.

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

exportconst 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 providedInare not loaded into the test, but are lazily instantiated, only in those 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 set of rules called tslint mentioned here 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 the simplest one tslint --fix! After all, if you do not know yet, tslintthere is an optionfix, automatically correcting all the errors that it finds! It can be used even more simply: install rxjs-tslintand run globally rxjs-5-to-6-migrate -p src/tsconfig.app.json. I tried rxjs-tslintone of our projects, and it worked quite well (run it at least twice to also roll up all the 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 up to 0 in the Chilean peso).

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

In general, there is also access other convenient formatting features, such as formatDate, formatCurrency, formatPercentand formatNumber.

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

Animations

In Angular 6.0, animations are already possible without a polyfill web-animations-js, unless you use it AnimationBuilder. Your application can win a few precious bytes! In case the browser does not support the API element.animate, 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 @ViewChildor @ViewChildren, or even embed it directly ElementRef. The disadvantage in this case is: in Angular 5.0 or below said ElementRefget property nativeElementtype any.

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 recognized as undesirable, and what is fundamentally changing

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

preserveWhitespaces: Defaultfalse

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

ngModeland reactive forms

Previously, it was possible to provide the same form field and 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 here, and the whole mechanism worked, perhaps, not as you expected ( ngModel- it was a familiar directive to you for a long time, and the input / output of a directive formControlthat 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 to give a warning always(always), once(once) or never(never). The default is valid 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: a 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:

  • Assembly time
  • Set size

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 file tsconfig.json) 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 component PonyComponentthat takes a model as input PonyModel(with parameters nameand color) and displays a pony image (depending on the suit), as well as the name of a pony.

Looks like that:

@Component({
  selector: 'ns-pony',
  template: `<div>
    <ns-image [src]="getPonyImageUrl()"></ns-image>
    <div></div>
  </div>`
})
exportclassPonyComponent{
  @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):

exportfunctionView_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:

  • The structure created DOM, which contains the definition of the elements ( figure, img, figcaption), and the definition of their attributes text nodes. Each element of the DOM structure in an array of view definitions is represented by its own index.
  • Change detection functions; the code they contain checks whether the expressions used in the template result in the same values ​​as before. Here the result of the method is checked getPonyImageUrland, if it changes, the input value for the image component will be updated. The same applies to the name of a pony: if it changes, the text node containing this name will be updated.

The code generated by Ivy

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

exportclassPonyComponent{
    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 attribute templatecontains the equivalent of the usual ngfactory, with a slightly different structure. The function template, as before, will be launched at any change, but now it has two modes:

  • Creation mode: the component is only created, it contains the static DOM nodes that need to be created
  • The rest of the function is performed with each change (if necessary, updates the image source and the text node).

What does it change?

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

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 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, which 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, it still needs to be patched 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 features

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 the 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 continue using 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 during execution

Please note: the new rendering engine finally opens up a long-awaited opportunity for us and gives “i18n during execution”. At the time of this writing, it 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 maintain - 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 by 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 can now publish their AoT code directly to NPM!

Improved patterns

Now the generated code should give improved spectra, 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.

NgModulewill 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 be not many new features, but Ivy is definitely interesting for the future. Experiment with it - I wonder how you like it!

Only registered users can participate in the survey. Sign in , please.

The relevance of the topic Angular 6


Also popular now: