iJS Blog

International Javascript Conference
9
Sep

Innovations in Angular 10

As a maintenance release, Angular 10 mainly includes bug fixes and tooling enhancements. For example, a new strict mode uses stricter compiler checks and the CLI now warns when using CommonJS bundles that are difficult to optimize. Angular 9 brought the long-awaited Ivy compiler, which the team worked on for about two years. Now, work on version 10 was used to clean up. So, this is primarily a maintenance release with numerous bug fixes and internal improvements. Nevertheless, Angular 10 also has a few nice additions to offer. This article is all about those. If you want to upgrade an existing application to Angular 10, all you have to do is run ng update: ng update @angular/cli @angular/core

Strict Mode

Over time, both TypeScript and the Angular compiler have been given various options for more rigorous source code checking. All these options have one goal: early detection of programming errors. However, additional checks can break existing source code. For this reason, they are not activated by default but must be enabled via settings in TypeScript’s configuration.

To automate the activation of these checks for new projects, ng new now has a new switch –strict (box: “Small budgets at –strict”). It causes the CLI to generate the settings shown in Listing 1.

Small budgets with –strict
The –strict option also reduces the default performance budgets set up in angular.json to a quarter. For example, ng build already issues a warning if the initial bundle exceeds 500 KB and gives an error if it exceeds 1 MB. Without the –strict switch these limits are 2 MB and 4 MB. Since these are only listings in angular.json, these limits can be changed if necessary.

Listing 1

"compilerOptions": {
  [...],
  "forceConsistentCasingInFileNames": true
  "strict": true,
  "noImplicitReturns": true,
  "noFallthroughCasesInSwitch": true,
  [...]
},
"angularCompilerOptions": {
  "strictInjectionParameters": true,
  "strictTemplates": true
}

Table 1 provides an overview of these settings. Behind the strict property are other options that TypeScript activates. An overview is given in table 2.

 

 

 

Setting Description
forceConsistentCasingInFileNames References to TypeScript files must always be written the same way; the use of

import { FluxCapacitor } from './flux-capacitor.ts';

in one and

import { FluxCapacitor } from './Flux-Capacitor.ts';

in the other file is therefore not allowed.

strict Enables further optional checks for TypeScript (Table 2).
noImplicitReturns Functions must explicitly return a value; therefore the following function is not allowed, because only if true is passed explicitly a value is returned:

Function stuff(flag: boolean) {
  if (flag) { return 'A'; }
}
noFallthroughCasesInSwitch Prohibits Fallthroughs when using switch/case; in the following example a break must be inserted at the end of the first case branch:

Function sayHello(state: string): void {
  Switch (state) {
    Case 'talks too much':
       console.log('Moin');
    case 'normal mood':
      console.log('Moin');
      break;
    case 'tired':
    case 'angry':
      console.log('Na?');
  }
}

The combination of tired and angry is allowed because the same code is executed for both.

strictInjectionParameters The Angular compiler returns an error if no injection token can be found for a constructor parameter.
strictTemplates Additional checks are performed in the templates, e.g. whether a variable is compatible with the property of a subcomponent (with an input).

Table 1: Strict Mode settings

 

 

Setting Description
noImplicitAny Prohibits the implicit use of any, for example, on the parameter message:

Function trace(message) {
  console.debug(message);
}
noImplicitThis Prohibits the implicit use of this.
alwaysStrict Always uses the ECMAScript Strict Mode.
strictBindCallApply Types calls of bind, call and apply; without this setting these methods accept any as well as any.
strictNullChecks Forces checks against null and undefined.
strictFunctionTypes Restricts the use of function types; without this option, both assignments are valid in the following example taken from the TypeScript documentation:

declare let f1: (x: Animal) => void; declare let f2: (x: Dog) => void;
 
f1 = f2; 
f2 = f1;

Here we assume that Dog inherits from Animal; but when activating strictFunctionTypes the assignment

f1 = f2;

is invalid, because not every animal is a dog.

strictPropertyInitialization Forces properties that cannot be undefined to get an initial value; this can be done either by directly assigning a default value or by using a constructor.

Table 2: Settings behind the TypeScript option strict

Two of these options I want to take a closer look at here, even though they have been around for a while. I will start with strictNullChecks, because errors due to missing null checks are very common. Tony Hoare, who is regarded as the inventor of null references, has even spoken of a billion-dollar error [1].

The option strictNullChecks aims to remedy this. If it is activated, TypeScript no longer allows the assignment of null and undefined, unless a variable is explicitly declared to be able to handle null or undefined. For example, the log function in Listing 2 allows the parameter msg to take the value null.

Listing 2

Function log(msg: string | null): void {
 
  if (msg === null) {
    Msg = '<null>';
  }
 
  const len = msg.length < 30 ? msg.length : 30;
  console.log(msg.substr(0, len));
}

Therefore, TypeScript also forces the check against null, because otherwise, the subsequent access to the property length or the call of substr would lead to an error.

A prerequisite for strictNullChecks is that the included libraries must also support this mode. Angular, for example, already supports this mode since version 4.1.

The option noImplicitThis, on the other hand, prevents TypeScript from assuming the value any for this in cases of doubt. This would be the case in Listing 3, for example, since the value of this in the function returned by createSwitch determines the caller.

Listing 3

export class AppComponent {
 
  lightsOn = false;
 
  createSwitch(): () => void {
    return function(): void {
      // this is NOT a reference to 
      // the current AppComponent instance!
      this.lightsOn = !this.lightsOn;
    };
  }
}

A common mistake here is to believe that the this in this function still refers to the current object of the AppComponent class. This error is also present in Listing 3. Without noImplicitThis it would remain undetected during compilation. If noImplicitThis is activated, however, the compiler complains because the type of this is not known and therefore it cannot verify that the property lightsOn exists.

This can be remedied by explicitly defining the type of this (function(this: MyType) { … }) or by binding this so that it points to the current AppComponent instance. This can also be done implicitly with a lambda expression since lambda expressions always bind this from the bound context (Listing 4).

Listing 4

export class AppComponent {
 
  lightsOn = false;
 
  createSwitch(): () => void {
    return () => {
      // this IS a reference to the 
      // current AppComponent instance
      this.lightsOn = !this.lightsOn;
    };
  }
}

TypeScript Solutions

Angular now also uses TypeScript Solutions. This means that the central tsconfig.json consists only of references to the actual TypeScript configurations of the individual projects and build targets (Listing 5).

Listing 5

{
  "files": [],
  "references": [
    {
      "path": "./tsconfig.app.json"
    },
    {
      "path": "./tsconfig.spec.json"
    },
    {
      "path": "./e2e/tsconfig.json"
    },
    {
      "path": "./projects/demo-lib/tsconfig.lib.json"
    },
    {
      "path": "./projects/demo-lib/tsconfig.spec.json"
    }
  ]
}

With it, IDEs will be able to recognize more easily which cinfiguration file is valid for which part of a solution. Additionally, the CLI creates a file tsconfig.base.json with default settings in the project root. All other configurations inherit from it and can override the default settings if necessary.

Warnings with CommonJS

Although an official module format has existed since ECMAScript 2015, many packages are still delivered in the form of CommonJS modules. However, this module format, which originates from the NodeJS world, has a disadvantage: it limits the possibilities of Tree Shaking. Unused package components cannot be detected and removed during the build process.

Therefore ng build will now issue a warning if one or more CommonJS Bundles are referenced (Fig. 1).


Fig. 1: Warning when using CommonJS Bundles

Ideally, the development team should take this warning as an opportunity to look for alternatives that support Tree Shaking. If this is not possible, they can explicitly allow the use of the package in angular.json under allowedCommonJsDependencies and thus suppress the warning for it (Listing 6).

Listing 6

"architect": {
  "build": {
    "builder": "@angular-devkit/build-angular:browser",
    "options": {
      "allowedCommonJsDependencies": [
         "js-sha256"
      ],
      [...]
    }
  }
}

Angular libraries become leaner

The Angular Package Format [2] is a specification that defines how npm packages are to be provided for Angular. This concerns the framework itself as well as packages from third parties. This is intended to ensure that the build of Angular applications runs as smoothly as possible and that the build tools can exploit their optimization potential.

In order to support as many environments as possible, the Angular Package Format in the past required that each package must provide the offered library in five different bundle formats:

  • ESM5: ECMAScript 5 + ECMAScript Module
  • FESM5: Flat ECMAScript 5 + ECMAScript Module
  • ESM2015: ECMAScript 2015 + ECMAScript Module
  • FESM2015: Flat ECMAScript 2015 + ECMAScript Module
  • UMD (Universal Module Definition)

The addition of “Flat” means that the entire library is combined into a single file. This speeds up the build process of the Angular applications based on it and also results in smaller bundles. Starting with version 10, the two ECMAScript 5-based formats are no longer mandatory. In addition, ESM2015 was already marked as deprecated with version 9. This should lead to a reduction of npm-packages and thus, faster downloads. The command ng build or the package ng-packagr used by it already takes this into account. Figure 2 shows the contents of the dist-folder in a built Angular library.

These folders contain the FESM2015 bundles (festm2015), the ESM2015 bundles (esm2015), and the UMD bundles. The folder lib contains TypeScript typings for the bundle.


Fig. 2: The Angular Package Format provides only (F)ESM2015 bundles and UMD bundles

Internet Explorer support is now an opt-in feature

To support Internet Explorer, ng build had to create ES5 bundles. In recent CLI versions, this command also created ES2015 bundles for modern browsers by default. Since these are smaller, this results in performance advantages. The disadvantage was longer build times.

Angular 10 now changes this behavior: By default, only ES2015 bundles are created. If you want to support Internet Explorer, you have to specify this in the .browserslistrc file. In this case, additional ES5 Bundles will be generated again.

Listing 7 shows the standard structure of the control file .browserslistrc. This file describes the browsers that will be supported. The documentation under [3] describes all possible entries. In order to support Internet Explorer, only the not has to be removed. This way, even versions 9 and 10 are supported. However, support is currently also marked as deprecated, and Angular 11 will only support Internet Explorer 11.

Listing 7

last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions

Firefox ESR
not IE 9-10
not IE 11

Further innovations

Besides the bigger innovations shown here, there are a lot of smaller ones. Below are three examples:

  • The Angular team handled over 700 issues reported on GitHub. This course will be continued in the future.
  • Angular Material offers a Date Range Picker [4].
  • Resolvers can now also abort navigation by returning an empty observable. The constant EMPTY can be used for this.

Conclusion

Not much has changed with Angular 10 – and quite deliberately so, especially since the focus after the introduction of Ivy was on cleaning up. The need for this is more than understandable, especially since almost the entire base of Angular was renewed with Ivy.

Nevertheless, there are some nice new features – especially in the tooling. This evolutionary advancement should come in handy especially for enterprise applications, which Angular was primarily created for. Here, continuity and stability are always required anyway.

But I also think that this was the calm before the storm because Ivy has a lot of potential. I’m thinking, for example, of stand-alone components that do not require Angular modules, optimized bundles for web components, and dynamic components. In addition, there are some innovations in the web world from which Angular can also benefit, for example, the upcoming webpack 5 with Module Federation.

Sources

[1] https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/
[2] https://g.co/ng/apf
[3] https://github.com/browserslist/browserslist
[4] https://www.daterangepicker.com

STAY TUNED!

 

BEHIND THE TRACKS OF iJS

Angular

Best-Practises with Angular

Vue.js

One of the most famous frameworks of modern days

JavaScript Practices & Tools

DevOps, Testing, Performance, Toolchain & SEO

Node.js

All about Node.js

React

From Basic concepts to unidirectional data flows