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