days
-4
-2
hours
-2
-1
minutes
-2
-7
seconds
0
0
search
New Features in the Framework and CLI

What’s new in Angular 6?

Manfred Steyer
©Shutterstock.com/gdainti

Angular Elements allows the provision of web components and the still experimental view engine ngIvy promises unrivalled small bundles. The CLI automates tedious tasks when integrating and updating libraries and can now build npm packages.

It’s that time again: Angular 6 is ready to set off. And with this version there’s also a new Angular CLI launching. Since its version number is to be kept synchronized with Angular from now on, it too jumps straight to version 6.

The changelog contains some new and trendsetting features, but also many other new inclusions to round things off, like an additional switch here and a new parameter there. In this article I focus on the former and showcase the new possibilities with some examples. As usual all of these examples can be found on my GitHub account.

Web Components with Angular Elements

Until now Angular has been best suited for comprehensive solutions. However, if you only wanted to add interactive areas to existing applications, other frameworks were often the first choice. An example are static pages, which are rendered by a CMS and have to be enriched with JavaScript widgets.

With Angular Elements, Angular is now advancing into this lightweight area by providing Angular components as Web Components. Strictly speaking we must speak of custom elements, especially since Web Components is a collective term for various technologies. No matter how you look at it, in the end it revolves around framework-independent components which behave like standard HTML elements.

Once the responsible npm package @angular/elements has been installed, any angular component can be converted into a custom element using the createCustomElement method (listing 1).

[…]
import { createCustomElement } from '@angular/elements';

@NgModule({
  imports: [ CommonModule ],
  declarations: [FlightCancellingComponent],
  entryComponents: [FlightCancellingComponent]
})
export class FlightCancellingModule { 
  
  constructor(injector: Injector) {
    const ngElementConstructor = createCustomElement(FlightCancellingComponent, {
      injector
    });

    customElements.define('flight-cancelling', ngElementConstructor);
  }
  
}

To connect the created element with the dependency injection mechanism of Angular, the example also passes the current injector. The method customElements.define, which registers the element with the browser and assigns it a tag name, is already part of the custom elements specification.

Since the underlying angular component is created dynamically if required, it must be entered under entryComponents. This is necessary so the Angular Compiler can detect it, although it is not connected to the other Angular components at the time of compilation.

It is irrelevant where or when the custom element is created and registered. As soon as the necessary statements have been executed, the application can use the new HTML element independently of the framework. An operation within Angular could look like this:

<flight-cancelling [flightId]="17" (closed)="handleClosed()">

Alternatively, the custom elements can also be dynamically added to a page at runtime (listing 2).

@Injectable()
export class SimpleFlightCancellingService  {

    show(flightId: number): void {
        const flightCancelling = document.createElement('flight-cancelling');

        flightCancelling['flightId'] = flightId;
        flightCancelling.addEventListener('closed', () => document.body.removeChild(flightCancelling));

        document.body.appendChild(flightCancelling);
    }
 
}

Only DOM board methods are used for this. In contrast to the dynamic generation of angular components, these are very straightforward and make such scenarios a lot easier.

Another field of application for Angular Elements is the orchestration of micro apps. The idea behind it is to create several smaller applications instead of one large application. Among other things, this is intended to reduce complexity and increase maintainability. In the form of custom elements, such apps can easily be dynamically loaded into a frame application and presented to the user as a whole.

One of Angular Elements’ current problems is that even the smallest component is dependent on Angular. This of course leads to unnecessarily inflated bundles. So far this isn’t a problem as long as Angular is used in the application anyway. However, if you only have to bundle large parts of Angular because of simple custom elements then this leads to an unnecessary overhead. The new view engine ngIvy, which is introduced in the next section, can help here.

Visions of the Future: Smaller bundles thanks to ngIvy

Most of the resources involved in the implementation of Angular 6 have gone into a very innovative concept: The new view engine ngIvy. It compiles Angular templates to very DOM-like code and is optimized for Tree Shaking. This means that only those parts of Angular which are actually needed are bundled.

For example: If you only want to provide simple custom elements with Angular, Angular can be blown away almost completely. The results are quite impressive: As the Angular team at ngconf 2018 announced, a simple Hi-World application can shrink to an impressive 2.7 KB if compressed.

The best thing about ngIvy is that application developers should not notice this, except for the smaller bundle sizes. It is a replacement of an implementation under the bonnet. This backward compatibility is targeted for one of the next Angular versions, ideally version 7 in autumn. Until then ngIvy is an experimental feature, hidden behind a switch.

Treeshakable providers

Although the Angular team relied on tree shaking to reduce bundle sizes right from the start, services were not treeshakable until now. This means that all services of the application as well as those of libraries have always been bundled – regardless of whether they were used or not. The reason for this is the way providers for services are declared (figure 1).

Abbildung 1: Klassische Service-Provider sind nicht treeshakable

Figure 1: Classic service-provider are not treeshakable

Since providers are defined at the module level, there is always an indirect link between the application and the service, even if it does not reference the service. Therefore, tree shaking algorithms decided against removing the services.

The solution found by the Angular team is very simple in principle: You simply turn over one of the arrows (figure 2).

Abbildung 2: Treekshakable Providers

Figure 2: Treekshakable Providers

Due to the change, the module will no longer refer to the service, but rather the service will now refer to the module. And this enables tools to recognize it, if an application uses the service.

It looks like this in code:

@Injectable({ provideIn: 'root'})
export class SimpleFlightCancellingService { […] }

The property provideIn does specify the module for whose scope the service is to be set up. And the string constant root will suffice for most of these cases. This is not only the scope of the main module though, but also the scope of all the other modules, which are loaded into the browser together with the main module. Only modules, which were purchased via lazy loading, receive their own scope.

In other cases where an indirection is required between a requested token and the service, which is supposed to be actually injected, further properties should be added:

@Injectable({
    providedIn: 'root',
    useClass: LazyFlightCancellingService,
    deps: [NgModuleFactoryLoader, Injector] 
})
export class FlightCancellingService { }

The property provideIn does specify the module for whose scope the service is to be set up. And the string constant root will suffice for most of these cases. This is not only the scope of the main module though, but also the scope of all the other modules, which are loaded into the browser together with the main module. Only modules, which were purchased via lazy loading, receive their own scope.

In other cases where an indirection is required between a requested token and the service, which is supposed to be actually injected, further properties should be added:

In addition to useClass other well-known properties can also be used here: useValue, useFactory and useExisting.

Remove whitespaces

Another way to optimize bundle sizes is to remove whitespaces from the templates. These make up 7% to 13% in many applications. Angular has mastered this task for a long time, but up until now it had to be explicitly activated. From version 6 it will be activated by default. In the very unlikely case where you still need the whitespaces, the preserveWhitespaces switch in tsconfig.json or tsconfig.app.json can be used for CLI projects:

"angularCompilerOptions": {
    "preserveWhitespaces": true
}

The same option is also available for individual components in the @Component decorator.

Architect: On the way to Bazel and Co?

The Angular CLI uses the popular webpack solution to tie bundles. In order to support alternative tools in the future, the CLI team has created a plug-in system called Project Architect. While the standard plugins, which are delivered with the CLI, delegate to webpack, it is now possible for everyone to also integrate tools. Which plugins are used for which tasks has to be entered in the control file angular.json, which replaces the previous .angular-cli.json. As a rule, however, this does not have to be a burden, as the CLI takes care of it when generating the project.

It is likely that the CLI team will use this mechanism to integrate the in-house Bazel as an optional build tool in the future. Unlike webpack, this language-neutral tool promises an integrated solution that can also be used to build server-side applications. Furthermore, Bazel promises extremely fast behavior even with huge applications, because it consistently relies on incremental compilation.

But there are also other bundling variations, which can be integrated. An example of this is a variant that takes server-side rendering into account.  And there is another long awaited feature, which is also based on Architect: the ability to generate npm packages, which will be presented in the next section.

Npm packages with the new CLI

You can use the npm packages of the JavaScript world to create reusable libraries. And they can be versioned and distributed through an internal or public registry.

Until now you had to use community projects to create such packages. But in the future CLI will take over this task on request. In order to do this you just simply add a library to an existing CLI project:

ng generate library mylib

It also makes sense to generate a demo application for testing:

ng generate application playground-app

Abbildung 3: Projekt mit Bibliothek und Test-Anwendung

Figure 3: Project with a library and a test application

As usual, the standard application is located under src. If you do not need it you can simply delete this folder. But then you also have to remove the corresponding entry from angular.json.

The statement

ng build --prod

builds all components of the project. For libraries, the CLI delegates to ng-packgr via Architect. This is a popular community solution for generating npm packages for Angular.

After the build process you can find the library under dist/mylib and publish it with npm publish. For the in-house use, this statement provides the –registry switch, which allows you to use a private npm registry.

Now, the CLI registers a mapping in the file tsconfig.json, so that the playground app can find the library. This can also be customized to be better suited to your own needs.

"paths": {
   "@my/logger-lib": [
      "projects/logger-lib/src/public_api.ts"
   ]
}

This entry causes all TypeScript imports which address @my/logger-lib to refer to the source code files of the library. After the build you could rearrange this entry so that it refers to the compilations in the dist-order. If you remove the entry, then TypeScript assumes that the library is located next to the other installed packages in the node_modules folder. This makes it easy to test different stages of the library. As an alternative to constantly modifying tsconfig.json, you can also use several tsconfig.json files. In this case the file, which is supposed to be used for this, has to be given over to the ng_build.

The possibility shown here is not only interesting for those who want to build npm packages. It is also suitable for subdividing large Angular projects into smaller sub-projects for better maintainability. Those who like this idea will also like the CLI-based project Nx, which offers additional tools and code generators for such scenarios.

Lazy loading without router

Lazy Loading is an important feature, especially for large applications: Instead of loading everything at program start, only the most important modules are loaded into the browser at the beginning and the rest is loaded by the application if required. For using lazy loading, the router offers a very comfortable API that hides the less handy, low-level APIs, which are enabling lazy loading in the first place.

But supporting Lazy Loading at runtime is only one side of the coin. The build tools must also play along and create bundles for individual areas. The CLI has had this task firmly under control since its first version: It checks the source code for corresponding routing entries and splits off additional bundles.

However, in cases where application developers want to use the low-level APIs for more control, the CLI has not provided support to date. But this changes now with version 6: Here you can enter modules into the control file angular.json, which are to be stored in their own bundles:

"options": {    
    […],
    "lazyModules": [
        "src/app/flight-cancelling/flight-cancelling.module"
    ]
}

Such modules can then be loaded at runtime, with Angular’s NgModuleFactoryLoader. To illustrate its use, the service in listing 3 loads a module with an angular element.

@Injectable()
export class LazyFlightCancellingService extends SimpleFlightCancellingService {

    constructor(
        private loader: NgModuleFactoryLoader,
        private injector: Injector
    ) {
            super();
    }

    private moduleRef: NgModuleRef;

    show(flightId: number): void {
        
        if (this.moduleRef) {
            super.show(flightId);
            return
        }

        const path = 'src/app/flight-cancelling/flight-cancelling.module#FlightCancellingModule'
        this
            .loader
            .load(path)
            .then(moduleFactory => {
                this.moduleRef = moduleFactory.create(this.injector).instance;
                console.debug('moduleRef', this.moduleRef);
                
                // Inherited Base-Implementation creates Element
                super.show(flightId);
            })
            .catch(err => {
                console.error('error loading module', err); 
            });
        
    }

}

It’s interesting to see, how the NGModuleRef instance, which describes the loaded module, is no longer used at all. For the module it is sufficient enough to set up the Angular element (see Listing 1). From then on, like any other HTML element, it can be created with document.createElement. To do this, this service inherits from the service in Listing 2, which takes care of exactly this task.

Just like with modules, which are reloaded the via router, it has to be stated that they must not be imported from a module, which was loaded at program start. If this is the case the module ends up in the main bundle, which leads lazy loading ad absurdum.

Submitting and updating libraries with Schematics, ng-add and ng-update

A very monotonous and therefore annoying task is the set-up of packages, which you load into a JavaScript application. For example, think of the library Angular Material: After obtaining it with npm install you have to import the correct Angular modules from this package, include a stylesheet file and also assemble the basic framework of the application from individual components.

To automate such tasks, CLI has been using the scaffolding tool Schematics for some time now. Each library can offer its own generators for standard tasks. These are triggered with the known command ng generate.

Furthermore, the CLI now also offers two additional commands: ng add and ng update. The former obtains a package via npm or yarn and calls a Schematic contained in it, which is named ng-add. This can take care of setting up the library right on the spot.

The ng update statement obtains the latest version of a library and executes defined Update-Schematics to update the existing application. Listing 4 demonstrates the use of these possibilities.



npm install @angular/service-worker
ng add @angular/pwa

ng add @angular/material
ng add @angular/cdk

ng generate @angular/material:material-table --name table
ng generate @angular/material:material-nav --name nav
ng generate @angular/material:material-nav --name dashboard

This example uses ng add to obtain the new @angular/pwa package, which uses Schematics from the Angular application to make an offline-enabled progressive web app leveraging @angular/service-worker. Angular Material is set up in the same way. The ng generate statement then generates the frequently required boilerplate, which is based on Angular Material. If you call this boilerplate in the application, the progressive web app obtained in this way looks like in figure 4.

 Abbildung 4: Generierte Angular Material PWA


Figure 4: Generated Angular Material PWA

In the future, ng update will also be available for updating to the latest Angular, CLI and Material versions:

npm install -g @angular/cli
npm install @angular/cli
ng update @angular/cli
ng update @angular/core

Within these lines it is noticeable that the CLI is obtained directly via npm install. This is necessary when migrating to version 6 to solve a chicken/egg problem, because first you have to have an Angular version that supports ng add and ng update.

Breaking Changes and Long Time Support Version

Even if a major release may in principle contain breaking changes, the further development of Angular is still very evolutionary. The team attaches great importance to backwards compatibility: new features, such as ngIvy, are initially classified as experimental and hidden behind a flag.

If you encounter problems during the update, you will find a step-by-step guide in the Update Guide to get from the version you are currently using to the latest one. Especially with the RxJS dependency there have been a few cleanups, but these can be largely automated with the information linked there or taken into account with a shim.

In principle, the Angular team recommends using the latest versions as much as possible. As some of the explanations here have shown, this often also brings noticeable performance improvements, since the product team constantly carries out optimizations under the bonnet.

For those who are not always able to switch to the latest versions, there are Long-Time Support versions. The product team at ngconf 2018 also had a pleasing announcement in this regard: in the future there will be long-time support for each version – not just for every second version as previously planned. This period lasts twelve months and begins with the release of the next version. This means, for example, that every single version of Angular is supported for 18 months and receives security-related updates and bug fixes.

Resume

Version 6 proves once again just how solid the foundation Angular actually is. It allows, for example, initiatives like ngIvy, where the entire view engine is exchanged. Thus existing applications receive a noticeable performance improvement by simply updating to the latest version. The published 2.7 KB, which currently requires a Hello-World registration (which is also based on it), is more than competitive. New possibilities like treeshakable providers or whitespace removal take the same line and once again show that performance is at the top of the list of architectural goals.

By combining ngIvy and Angular Elements, Angular is now advancing into areas previously dominated by smaller frameworks. The Angular team keeps their original promise to rely on Web Components with Angular Elements.

On the other hand, the CLI is increasingly helping to make the use of Angular as simple as possible. Thanks to Architect, new build tools such as ng-packagr for building libraries can be integrated and Schematics helps to set up, use and update libraries by automating monotonous and error prone tasks.

Author
Manfred Steyer
Manfred Steyer is an independent trainer and consultant, focussing on Angular 2. In his current book, "AngularJS: Moderne Webanwendungen und Single Page Applications mit JavaScript", he covers the many different aspects of the popular JavaScript-Frameworks made by Google.

Leave a Reply

1 Comment on "What’s new in Angular 6?"

avatar
400
  Subscribe  
Notify of
Luke
Guest

Thanks for the great article!