Bulletproof Angular. Angular strict mode explained
Angular uses TypeScript because TypeScript provides us with the tooling to create more robust applications. I'm talking about tools for type safety. But tons of developers aren't using the provided tools. They just create applications as they did 10 years ago using JavaScript.

Angular uses TypeScript because TypeScript provides us with the tooling to create more robust applications. I'm talking about tools for type safety. But tons of developers aren't using the provided tools. They just create applications as they did 10 years ago using JavaScript.
Also, Angular provides additional tools for checking our apps and those checks aren't enabled by default. Most developers have no idea about them.
All the cool tools for safe Angular applications will be enabled in new apps if you'll just add --strict=true
flag when creating a new app through the Angular CLI.
In this article, I'm discussing the strict mode in Angular and how exactly it will help you build more robust Angular applications. Today we'll cover:
- TypeScript strict mode
- Avoid usage of
any
- No fallthrough cases in Switch blocks
- Angular strict mode
TypeScript strict mode
Let's start with what I consider the most important tool - TypeScript strict mode. To enable it you specify strict: true
in a tsconfig.json
:
{
"compilerOptions": {
"strict": true
}
}
This flag will enable the following options for the TypeScript compiler: noImplicitAny
, noImplicitThis
, strictNullChecks
, strictPropertyInitialization
, strictBindCallApply
, strictFunctionTypes
.
We'll not cover all of them, you can take a deeper look at this great article. But we'll cover three of the most important: strict null checks, noImplicitAny, and strictPropertyInitialization.
strictNullChecks
By default, it's okay to assign null or undefined to any value at TypeScript.
let someVariable: number = 666;
// Assigning null and everything is ok
foo = null;
// Assigning undefined and everything is ok
foo = undefined;
But in the strict mode, this code will throw an error since null and undefined aren't assignable to the type number. That's why, if you need to assign a null value to this variable, you ought to tell the TypeScript compiler that it can be a number or null.
let someVariable: number | null = 666;
// Assigning null and everything is ok
foo = null;
// Assigning undefined and IT'S AN ERROR!
foo = undefined;
Moreover, null and undefined are different for the TypeScript compiler! So, it's impossible to assign undefined to the variable that's declared as null. To fix that you ought to make the variable either null or undefined.
let someVariable: number | null | undefined = 666;
// Assigning null and everything is ok
foo = null;
// Assigning undefined and everything is ok
foo = undefined;
Did you know this? Looks pretty mind-blowing for me, However, this technique helps us track where and what values are assigned. TypeScript compiler with strictNullChecks flag will throw an error when you assign null or undefined to the variable without null | undefined in its type. Such behavior will make your code more robust and simpler to read. Since you’ll be sure no empty value is assigned to the variable without null | undefined in its type.
strictPropertyInitialization
This flag forces the author of the code to initialize all the class properties in the constructor. For example, this code will throw an error.
export class AppComponent {
// No initializer here, tsc will throw an error
title: string;
}
To fix it, you ought to initialize the title variable in place or at the constructor.
export class AppComponent {
// In place initialization
title: string = 'some text';
// Initialization in constructor works to
constructor() {
this.title = 'some text';
}
}
Also, if you don't want to initialize it by default in your class, you can shut the compiler up and tell him: 'Trust me, I know what I'm doing'. To do so, please, add an exclamation point like so.
export class AppComponent {
title!: string;
}
This technique allows us to make sure that everything is initialized when a new object is created. So, you don’t need to check whether the prop is initialized or not. TypeScript will take care of this. You can be sure that every prop has data.
noImplicitAny
This one will force you to add types for all your variables (yep, we're almost turning Angular code into Java code). That's why the TypeScript compiler will throw an error during compilation for this function:
// No type declaration
function foo(bar): void {
console.log(bar);
}
To make it work, you need to add a type to the bar:
function foo(bar: string): void {
console.log(bar);
}
This will work. That's the most important rule that forces my teammates to hate me from time to time. Because they ought to add types in their apps! However, noImplicitAny guarantees that no one leaves a variable which type can’t be inference by the TypeScript compiler. Anyway, you can use any there:
function foo(bar: any): void {
console.log(bar);
}
Not typesafe enough, right? For sure, but it’s a valid code from the TypeScrip compiler point of view. Here we have the only way - disallow usage of any (explained below).
As you can see above, adding a strict flag to your tsconfig.json will add you a few cool configurations and force you to:
- Not use null in your apps when you don't know about that.
- Always declare your variables and make sure they will have data when you'll access them.
- Add types for your variables making them easily readable and more robust.
That's all I want to tell you about the strict mode. Now let's dive into one of the funniest rules I'm introducing in this article.
Not using Any
Yep, we don’t want to use any. This rule forces you to NOT use type any in your project. You can enable it via adding no-any: true
in your tslint.json
.
{
"rules": {
"no-any": true
}
}
Creating applications without any makes your apps more robust. If your app uses no-any, you can make sure that every type in the app will be defined and each developer will understand what each variable in the codebase means.
Also, the TypeScript compiler will bark on you any time you're not defining the type of the variable and you'll have to do that. The usage of the no-any with strictPropertyInitialization, noImplicitAny, and strict null checks will make your code bulletproof. So, don't hesitate, start your project with them.
noFallthroughCasesInSwitch
This one is great. I like it. It forces you to not use fall-through case statements. That's why this will throw an error.
const foo: number = 0;
switch (foo) {
case 0: // The error will be thrown here
console.log('0');
case 1: // No error here
console.log('1');
break;
}
Because you have no break statement after the first console.log that's considered a bad practice. Please, don't use the switch/case that way. What's acceptable? Using break statements is ok as you can see above. Also, it's acceptable to use empty fall-through case statements.
const foo: number = 0;
switch (foo) {
case 0: // No error here
case 1: // No error here
console.log('1');
break;
}
The code above is much better. Why? Because if you have a fall through case statement you’re preparing the ground for tons of unexpected errors. Multiple case statements will be executed all the time. Other people will never expect that by default. While at the fixed version the code flow is predefined and pretty simple.
That's all about cool TypeSctrict checks I wanted to tell you in this article. Now, let's go higher - talk about Angular strict checks
Angular strict mode
Angular does a few checks in the template by default but they can't guarantee that your app will work properly. Enabling Angular strict mode through the Angular CLI will enable TypeScript strict mode in your app also as enable some Angular strict checks. Angular Strict template checking is enabled via strictTemplates
in tsconfig.json
. It'll force the Angular compiler to check tons of things.
Proper data assignment to component’s inputs
Assuming we have the following code:
@Component({
selector: 'app-child',
template: ' {{ title }}',
})
export class ChildComponent {
@Input() title: string = '';
}
@Component({
selector: 'app-root',
template: `
<app-child [title]="12345678"></app-child>
`,
})
export class AppComponent {}
Focus on the title
input of the ChildComponent
. It has a string
type. While we’re trying to push a number inside. Angular compiler detects it and throws an error. Plain type error.
Pipes have the correct return type
Let’s get back to the code, but add a pipe there:
@Pipe({ name:'testPipe'})
export class TestPipe implements PipeTransform {
transform(value: any, ...args: any[]): number {
return 0;
}
}
@Component({
selector: 'app-child',
template: ' {{ title }}',
})
export class ChildComponent {
@Input() title: string = '';
}
@Component({
selector: 'app-root',
template: `
<app-child [title]="'Hey! Im a title' | testPipe"></app-child>
`,
})
export class AppComponent {}
Again, we have a child component with title input. It has a type string and we’re passing the string inside. However, we’re passing it through the testPipe
that has a number
return pipe. And here is the problem. The resulting type of the 'Hey! I’m a title' | testPipe
is a number. While ChildComponent#title
receives only strings. Such behavior will throw an error.
The correct type of event$'s
This check guarantees that every event will have a proper type in the template.
@Component({ selector: 'app-child' })
export class ChildComponent {
@Output() titleChange: EventEmitter<string> = new EventEmitter();
}
@Component({
selector: 'app-root',
template: `
<app-child (titleChange)="update($event)"></app-child>
`,
})
export class AppComponent {
update(title: number): void {}
}
Here we have almost the same code as before. While now we're working with the titleChange
event. That has a string type. However, I’m trying to assign an update(title: number)
callback to it that is unsuitable here because of the number
type instead of string
.
So, I would say strict template checking makes your app more robust and easier to support. To get a detailed explanation of the strict template checking, please, refer to this doc.
Do I need a strict mode?
Angular provides us with tons of flags that allow configuring what strict features will be turned on. But most of the important flags will be turned on if you'll create an app through the ng new my-app --strict=true
command. Strict compilation mode will force you to take types into account, use nulls, and undefined properly. That's why I would say that strict mode is a must for your app.
BUT. it's cool until it's not. From time to time strict mode gives you surprises. For instance, how do you feel this code will work with Angular strict mode:
@Component({
selector: 'app-child',
template: `{{ title }}`,
})
export class ChildComponent {
@Input() title: string = '';
}
@Component({
selector: 'app-root',
template: `
<app-child [title]="title$ | async"></app-child>
`,
})
export class AppComponent {
title$: Observable<string> = of(`I'm a title`);
}
That's a pretty common approach - use a stream and pass it inside the @Input using the async pipe. However, if you dive inside the async pipe source code you'll see that it will pass null first of all inside the input, and only when title$ fires it'll pass that value inside the input. That's why the type of the title$ is Observable<string>
but the type of the title$ | async
is Observable<string | null>
;
BUT 2.
Don't turn it on in the project that's already running in the production. Don't turn it on for the team that used to build projects without strict mode - strict mode will freak your teammates out. They will hate you. I made that mistake once. And as you can guess it didn’t go well.
Thank you for the reading! Follow me on Twitter to stay connected!