The first time I approached front-end development in Angular I was a bit off guard, especially for those like me who come from a back-end environment (specifically PHP) and are used to using strong typing within their projects (also thanks to static analysis tools such as Psalm or PHPStan).
Working for a year now on the construction of projects in Angular, in fact, I could not help but notice that, although the framework uses TypeScript extensively, we often tend to leave it in “silent” mode, not fully exploiting the potential of the compiler.
Even in the organization of the folders, I have often found a lot of confusion, with the tendency to insert too much logic in the components, when these should simply take care of taking the data and showing them by relegating the fetching of the data and the operations of manipulation of them, which do not concern necessarily the state of the components, to the services.
These scenarios I witnessed led me to create a series of articles to shed light on some concepts of Angular and the best practices to be adopted to improve development on this framework. This first article will indicate some tips on getting the most out of Angular installation and TypeScript configuration.
Angular Strict Mode
The first operation we can perform is to enable Angular strict mode already during the installation phase, using the command:
ng new [project-name] --strict
This mode enables:
- The strict mode of TypeScript and other flags that we will analyze
- Enable the Angular flags: strictTemplates and strictInjectionParameters
- Reduces the size of budgets by ~75%
TypeScript Strict Mode
Angular strict mode automatically enables the following TypeScript flags within the tsconfig.json file. These could also be enabled by manually editing the TypeScript configuration file:
strictPropertyInitialization
This flag signals a compile error if the class properties are declared, but not initialized in the constructor. Therefore, the following code snippet would throw an error:
@Component({...})
class AppComponent {
// Error: Property 'title' has no initializer
@Input() title: string;
}
To fix it we can initialize the property in the constructor or during the declaration:
@Component({...})
class AppComponent {
@Input() title = '';
}
strictNullChecks
This flag reports an error if we try to use null or undefined when we expect to receive a concrete value:
interface Person {
firstName: string;
lastName: string;
age: number;
}
// Error: Type 'null' is not assignable to type 'Person'
const developer: Person = null;
console.log(`${developer.firstName} ${developer.lastName}`);
noImplicitAny
This flag is my favorite because it allows an error to be reported if we leave any type as an inferred implicit type. This does not mean that we can no longer use any type (although I personally advise against its frequent use, because it makes the use of TypeScript useless), but that we simply have to explicitly specify the type (even if this is any) in every property, parameter and variable declared.
@Component({...})
export class AppComponent {
// Error: Parameter 'value' implicitly has an 'any' type
onAddClick(value) {
// Do stuff...
}
}
To correct the error we can explicitly indicate the type any or indicate a more specific type:
@Component({...})
export class AppComponent {
onAddClick(value: Person) {
// Do stuff...
}
}
strictBindCallApply
This flag is a little more anonymous, in summary, it allows TypeScript to verify the correctness of the types passed as a parameter even a function is called via the call, bind, and apply methods:
function toInt(x: string) {
return parseInt(x);
}
const number1 = toInt.call(undefined, "10");
// Error: Argument of type 'boolean' is not assignable to
// parameter of type 'string'
const number2 = toInt.call(undefined, false);
strictFunctionTypes
This flag verifies that when assigning functions the parameters and return values are compatible with the subtypes:
function log(x: string) {
console.log(x.toLowerCase());
}
type StringOrNumberFunc = (ns: string | number) => void;
// Error: Type '(x: string) => void' is not assignable to type 'StringOrNumberFunc'
const func: StringOrNumberFunc = log;
There are three other options that, although they are not automatically enabled by setting Angular strict mode, I highly recommend setting them manually:
noImplicitReturns
This flag allows TypeScript to report an error if all paths to a function do not return a value:
// Error: Not all code paths return a value
function lookupHeadphonesManufacturer(color: string) {
if (color === 'blue') {
return 'beats';
}
'bose';
}
noUnusedLocals
This flag allows a TypeScript to report an error if a declared variable is not used:
// Error: 'OnInit' is declared but its value is never read
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title: string = 'Devmy Example Project';
// Error: 'subtitle' is declared but its value is never read
private subtitle: string = 'Hello World';
// Error: 'log' is declared but its value is never read
private log(value: string): void {
console.log(value);
}
}
noUnusedParameters
This flag allows TypeScript to report an error if a function parameter is not used:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title: string = 'Devmy Example Project';
// Error: 'value' is declared but its value is never read
onOptionChange(value: number) {
}
}
If it’s mandatory to indicate the parameter, but it isn’t necessary to use it, we can simply tell TypeScript to ignore it by replacing or prefixing the parameter name with an underscore:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title: string = 'Devmy Example Project';
onOptionChange(_value: number) {
}
}
Strict Angular Template
By enabling Angular strict mode, these flags are also automatically activated:
strictTemplates
If enabled, Angular will check within the template files that type interface is complying:
// app.component.ts
interface User {
firstName: string;
lastName: string;
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title: string = 'Devmy Example Project';
users: User[] = [
{
firstName: 'Mario',
lastName: 'Rossi'
},
{
firstName: 'Maria',
lastName: 'Rossi'
},
{
firstName: 'Carlo',
lastName: 'Rossi'
}
];
}
// app.component.html
<div>
<ul>
<li *ngFor="let user of users">
<!-- Property 'age' does not exist on type 'User' -->
{{ user.firstName }}-{{ user.lastName }}: {{ user.age }}
</li>
</ul>
</div>
strictInjectionParameters
If enabled, Angular will report an error if it is not possible to determine which type to inject for the parameter specified in the constructor.
Angular Bundle Budgets
When running the build, the Angular application must respect the maximum allowed budgets. If our application exceeds this size, the build will fail. This forces us to deploy performing applications without overly “heavy” dependencies.
By default Angular has quite high budgets:
- Up to a build size of 2MB, we will receive a simple warning, exceeding 5MB an error will be displayed during the build phase
- Up to a component style size of 6KB, we will receive a simple warning, exceeding 10KB you will get an error during the compilation phase
By activating Angular strict mode, these budgets are reduced by ~ 75%, allowing us to immediately identify if we have introduced an excessively “heavy” dependency:
- Up to a build size of 500KB, we will receive a simple warning, exceeding 1MB an error will be displayed during the build phase
- Up to a component style size of 2KB, we will receive a simple warning, exceeding 4KB you will get an error during the compilation phase
To make sure we respect the size of budgets set we must:
- Use the source-map-explorer tool to inspect the impact of the dependencies installed within the application
- Use lazy-loading
- Avoid large imports into component styles
Conclusions
Although Angular strict mode may seem overly restrictive at first, I assure you that activating it will make your code more robust, less prone to bugs, and easier to update. From version 12 the Angular team has seen fit to make the strict mode as default mode, but, for pre-existing projects, it could be a godsend to go and activate the various flags!
In the next article, I will talk about the recommended tools to speed up the development and use of Angular in everyday life 🤓
Top comments (0)