days
0
-27
-7
hours
0
-2
minutes
-2
-1
seconds
-3
-2
search
Fully integrated Ivy, Lazy Loading components and localization

Angular 9 is here – Ivy, Lazy Loading and more

Manfred Steyer
Angular
© Shutterstock / anastasiia agafonova

Angular 9 brings Ivy in a backwards compatible version and, as a result, smaller bundles. In addition, the I18N solution has been extensively revised and some corners have been rounded off. This provides new possibilities not only for the developer but also for future versions of Angular.

With the release of Angular 9, it’s ready: Ivy will finally become the standard. This is possible because the Angular team invested a lot of energy to make Ivy backward compatible with its predecessor ViewEngine. A glance at the changelog reveals that this was exactly the focus of version 9. But apart from that, there are still some very nice new features. In this article, I will introduce the new features that will make daily work easier. I’ll use a few examples from my GitHub repository.

Update

The Angular 9 update is very straightforward, as you may be used to from previous versions. The CLI command ng update @angular/core @angular/cli will bring the libraries up to date. Possible breaking changes will be considered automatically as far as possible. This is made possible by migration scripts based on the CLI-internal code generator schematics, which slightly modify the existing source code.

In cases where we have to intervene manually, we use schematics to issue a message. You can also find more information about the migration as usual at Angular.io.

Ivy by default

The most important new feature of Angular 9 is probably the fact that the new Ivy compiler, which has been in development for many months, is activated by default. This means that our bundles will be up to 40 percent smaller after the switch to version 9. How much our applications can exploit this potential depends on how they are set up.

To avoid breaking changes, special attention was paid to backward compatibility. This has something to do with the fact that Google has over 1,500 Angular applications in use. These applications should, of course, continue to function after the switch to version 9. At the same time, they helped to ensure the quality of Ivy. In addition, a large number of widely used Angular libraries were used for quality assurance.

However, since the Angular team had to replace the entire substructure, Ivy is anything but an easy task. As a result, Angular Ivy can sometimes bring to light one or two errors in marginal cases. In these cases we can disable Ivy with the enableIvy property in tsconfig.json:

"angularCompilerOptions": {
  "enableIvy": false
}

The Angular team is interested in cases where this is necessary and is happy to receive bug reports.

Lazy loading of components

At first sight, Ivy brings smaller bundles. But Ivy’s architecture has a lot more to offer, so we can expect new features based on it in the next releases.

However, one new feature is already available today: lazy loading of components. While lazy loading was already integrated from the beginning, until now, whole Angular modules always had to be loaded. The reason was that ViewEngine placed the metadata for the use of the individual components at the module level. However, Ivy now stores this metadata directly in the components when compiling, so they can also be obtained separately.

To illustrate the use of this new possibility, I use a simple dashboard here, which displays the tiles via lazy loading (Fig. 1).

angular

Fig. 1: Dashboard with lazy loading

For this, we first need placeholders into which the component can be loaded. This can be any tag as long as it is marked with a template variable:

<ng-container #vc></ng-container>

Template variables begin, as you can see here, with a diamond. The associated component can load this element as ViewChild (Listing 1).

export class DashboardPageComponent implements OnInit, OnChanges {
 
  @ViewChild('vc', {read: ViewContainerRef, static: true}) 
  viewContainer: ViewContainerRef;
 
  […]
 
  constructor(
    private injector: Injector,
    private cfr: ComponentFactoryResolver) { }
 
[…]
}

In addition, we need the current injector and a ComponentFactoryResolver for the dynamic creation of the lazy component. Both can be injected into the constructor.

Afterward, everything is very straightforward: The lazy component can be loaded via a dynamic import and the ComponentFactoryResolver determines the factory we need to instantiate the component (Listing 2).

import('../dashboard-tile/dashboard-tile.component').then(m => {
  const comp = m.DashboardTileComponent;
 
  // Only b/c of compatibility; will not be needed in future!
  const factory =
    this.cfr.resolveComponentFactory(comp);
 
  const compRef = this.viewContainer.createComponent(
    factory, null, this.injector);
 
  const compInstance = compRef.instance;
 
  compInstance.a = Math.round(Math.random() * 100);
  compInstance.b = Math.round(Math.random() * 100);
  compInstance.c = Math.round(Math.random() * 100);
  compInstance.ngOnChanges();
});

The method createComponent of the ViewContainer receives the factory and creates an instance of the component. To ensure that it is connected to the Dependency Injection mechanism of Angular, the current injector must also be passed. The example then calls the component instance, sets properties, and calls the ngOnChanges method. The result is a DashboardTileComponent that appears in the middle of the ViewContainer.

Another small but nice innovation is that the application no longer needs to register such dynamic components in the entryComponents. This construct, which is difficult to understand for many people anyway, now only exists in case of a fallback to ViewEngine.

Better I18N with @angular/localize

The localization solution (I18N), which has been integrated into Angular since version 2, was trimmed for performance. It generates one build per locale (a combination of language and country) and adjusts the translation texts when compiling so that there is no overhead at runtime. However, this strategy also had a few disadvantages:

  • Creating all these builds was time-consuming.
  • It was not possible to determine the translation texts at runtime.
  • It was not possible to use translation texts programmatically.
  • The language could not be changed at runtime. Instead, the application had to redirect the user to the desired language version.

With Angular 9, a new solution appears that compensates for most of these disadvantages without compromising performance. It is called @angular/localize and can be obtained from the npm package of the same name. The new @angular/localize initially creates a single build. For each locale, it then creates a copy and inserts the translation texts there (Fig. 2).

angular

Fig. 2: Functionality of @angular/localize [courtesy of Minko Gechev from the Angular Team]


In addition, @angular/localize adds metadata to each copy for formatting numbers and dates according to the customs of the respective language. So we no longer have to import and register the required metadata at program startup. As usual in the Angular environment, the library can be obtained via ng add:

ng add @angular/localize

Then we can add the attribute i18n to the texts to be translated in the templates. To provide the translation studio with context information, this attribute is assigned a value with the meaning and a description:

<h1 i18n="meaning|description@@home.hello">Hello World!</h1>

Both pieces of information are optional and are separated by a pipe. Also optional is the ID, which can appear after two @-symbols. If this ID is missing, Angular generates one itself. However, this is problematic because if the markup changes, Angular assigns a new value for it.

After all of the texts have been marked with i18n, the command ng xi18n extracts them into an XML file. This XML file must be copied for each language and supplemented with appropriate translations (Listing 3).

Hello World!</source>
  <target>Hallo Welt!</target>
  <context-group purpose="location">
    <context context-type="sourcefile">src/app/app.component.html</context>
    <context context-type="linenumber">1</context>
  </context-group>
  <note priority="1" from="description">description</note>
  <note priority="1" from="meaning">meaning</note>
</trans-unit>

By default, these files are in XLF (XML Localization Interchange File) format. Alternatively, the CLI can be instructed to use XLF2 or XMB (XML Message Bundles) instead. These formats are often supported by translation studios.

The translated files must then be registered in the angular.json (Listing 4).

"i18n": {
  "locales": {
    "de": "messages.de.xlf",
    "fr": "messages.fr.xlf"
  },
  "sourceLocale": "en-US"
},

With a few additional properties, the selected format can also be specified if it differs from the standard XLF format. In addition, the locale in which the templates are available before translation must be entered as sourceLocale.

The statement ng build -localize now creates one version per locale (Fig. 3). As mentioned, this task can be done relatively quickly because the CLI compiles only once and then only exchanges the texts. If you now start a web server in the dist/project name directory, you can select one of the language versions by appending the respective locale to the URL.

angular

Fig. 3: Language versions in the dist folder.

However, the new @angular/localize also allows the use of the translation texts at runtime. So-called Tagged Template Strings are to be used for this:

title = $localize`:@@home.hello:Hello World!`;

The tag used here is the global identifier $localize. In addition, the string is introduced with the ID of the respective text. This is located between two colons. After that comes the default value. Currently, ng xi18n does not extract such texts automatically, but this does not prevent us from using existing entries in this way or from manually adding new ones to the XML files.

Another eagerly awaited possibility is to specify the translation texts at runtime. To support these runtime translations, they only need to be defined in the form of key/value pairs:

import { loadTranslations } from '@angular/localize';
 
loadTranslations({
  'home.hello': 'Küss die Hand!'
});

However, this has to be done before Angular is loaded, so this section of code has to be outsourced into a separate bundle. For this reason, the enclosed example shows this call in polyfills.ts In addition, the application itself has to take care of loading the correct metadata for formatting numbers and dates.

Any and platform

The treeshakable providers introduced with version 6 make working with services much easier. Version 9 takes it one step further by adding two more values for providedIn: any and platform:

@Injectable({ providedIn: 'any' })
export class LoggerConfig {
  loggerName = 'Default';
  enableDebug = true;
}

The setting any causes each scope to have its own service instance at the module level. This means that there is a separate instance for all lazy modules and another instance for all other non-lazy modules. This does not affect scopes at the component level.

This reduces the need for forRoot and especially forChild methods. However, the latter still have the advantage that they can also accept configuration parameters. When using any, the application must specify them in another way, for example, in the constructor of the respective module (Listing 5).

@NgModule({ […] })
export class FlightBookingModule {
  constructor(private loggerConfig: LoggerConfig) {
    loggerConfig.loggerName = 'FlightBooking';
    loggerConfig.enableDebug = false;
  }
}

The platform setting, which has also been added, registers a service in the platform injector. This is located one level above root and hosts Angular-internal services.

Dev server for server-side rendering

Although server-side rendering (SSR) is certainly not an issue that every project needs, it is still strategically important to the Angular team. It allows Angular to penetrate into the area of public, SEO-critical sites. Until now, however, the development of such solutions has been cumbersome, especially since the development web server only rebuilt the browser-based version of the project after a change, not the server-based version. To test the impact on a server-side operation as well, a new build had to be created.

This is now over, because a builder is available for SSR that rebuilds both versions and then updates the browser window. So we can immediately see all the effects of our changes.

In order to enjoy this solution, after adding the corresponding @nguniversal package, all you have to do is run the npm script dev:ssr:

ng add @nguniversal/express-engine@next
npm run dev:ssr

Since two builds have to run in parallel, the performance is not quite as good as with the classic development server. Nevertheless, it improves the developer experience significantly over the status quo.

Conclusion and Outlook

With Angular 9 we get the long-awaited Ivy. Thanks to its well-thought-out design, Angular itself makes it more tree shakable and therefore leads to much smaller bundles in many cases. It also allows lazy loading of components.

To support Ivy, the integrated I18N solution also had to be revised. The Angular team took this as an opportunity to improve the implementation: The build process has been drastically accelerated and we can programmatically provide translation texts, but also consume them. In addition, the setting any for providedIn makes it easier to configure libraries by ensuring that each lazy module has its own instance. It can be an alternative to forRoot and + and is a piece in the mosaic of efforts to make the Angular module system optional.

Although this all sounds very exciting, it only gets really exciting after Angular 9. Ivy offers potential for many innovations, which the Angular team can now devote itself to.

Author
Manfred Steyer
Manfred Steyer (https://www.softwarearchitekt.at/) is a trainer and consultant who supports software teams throughout the German-speaking world in the development of Angular. He is Trusted Collaborator in the Angular Team, was awarded as Google Developer Expert and writes for O'Reilly, the German Java Magazine and Heise Developer. In his latest book on Angular, he covers the many sides of the popular JavaScript framework written by Google.

Leave a Reply

avatar
400