Mastering TypeScript Strict Mode
A practical guide to enabling and working with TypeScript strict mode in Angular projects — avoid the common pitfalls and write code that is genuinely safer.
title: Mastering TypeScript Strict Mode slug: typescript-strict-mode summary: A practical guide to enabling and working with TypeScript strict mode in Angular projects. Avoid common pitfalls and write safer code. date: 2024-04-01 author: Sam Chen category: typescript tags: [typescript, angular, testing] status: published featured: false#
Mastering TypeScript Strict Mode#
TypeScript strict mode is the single most impactful compiler setting you can enable in an Angular project. It catches an entire category of runtime bugs at compile time, from null reference errors to implicit any types. If you are starting a new Angular 18 project, strict mode is enabled by default. For older projects, migrating to strict mode is a worthwhile investment.
What Strict Mode Enables#
The "strict": true flag in tsconfig.json is a shorthand that enables a bundle of strictness checks simultaneously. Understanding each flag helps you decide whether to enable them individually or all at once.
noImplicitAny#
By default, TypeScript infers the type any for variables and parameters that lack explicit type annotations. With noImplicitAny enabled, the compiler raises an error instead, forcing you to be explicit.
// Without noImplicitAny — compiles, but unsafe
function process(data) {
return data.toUpperCase();
}
// With noImplicitAny — compile-time error
function process(data) {
// ^ Error: Parameter 'data' implicitly has an 'any' type
return data.toUpperCase();
}
// Fixed — explicit type annotation
function process(data: string): string {
return data.toUpperCase();
}
In Angular templates, strictTemplates (part of the Angular compiler options) extends this concept to component bindings, ensuring that @Input() properties receive values of the correct type.
strictNullChecks#
This is arguably the most valuable strict flag. It makes null and undefined distinct types that are not assignable to other types unless explicitly declared.
interface User {
name: string;
email?: string;
}
function sendEmail(user: User): void {
// Without strictNullChecks — compiles but may crash at runtime
const address = user.email.toLowerCase();
// With strictNullChecks — compile-time error
// Error: Object is possibly 'undefined'
// Fixed — narrow the type first
if (user.email) {
const address = user.email.toLowerCase(); // OK — TypeScript knows email is string here
}
}
When working with Angular forms, strictNullChecks forces you to handle the fact that form controls may have null values, leading to more robust validation logic.
strictFunctionTypes#
This flag enforces stricter checking of function parameter types, particularly for callbacks and method signatures. It prevents unsound assignments where a function with more specific parameter types is assigned to a variable expecting more general parameters.
type Logger = (message: string) => void;
// Without strictFunctionTypes — allowed, but unsafe
const logger: Logger = (msg: string | number) => console.log(msg);
// With strictFunctionTypes — error: type incompatible
// A Logger must accept exactly a string, not string | number
strictPropertyInitialization#
Angular components often declare properties that are populated later by @Input() or in ngOnInit. With strictPropertyInitialization, you must either provide an initial value or use the definite assignment assertion !.
@Component({ ... })
export class ArticleCardComponent {
// Error: Property 'article' has no initializer
article: Article;
// Fix 1: definite assignment assertion
article!: Article;
// Fix 2: optional property
article?: Article;
// Fix 3: initialize with a default
article: Article = { id: '', title: '', slug: '' };
}
Angular-Specific Strict Settings#
Beyond the TypeScript compiler, Angular provides its own strictness flags in tsconfig.json under angularCompilerOptions:
{
"angularCompilerOptions": {
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
strictTemplates is particularly powerful. It type-checks component templates against the component class, catching errors like passing a string to an @Input() expecting a number.
Incremental Migration Strategy#
Adding strict mode to an existing codebase can produce hundreds of errors. Instead of fixing everything at once, enable flags incrementally:
- Start with
noImplicitAny— add type annotations to function parameters and variables. - Enable
strictNullChecks— add null checks and optional chaining where needed. - Turn on
strictFunctionTypesandstrictPropertyInitialization. - Finally, set
"strict": trueand let the compiler catch anything you missed.
Use // @ts-expect-error with a descriptive comment to suppress errors you cannot fix immediately. Unlike // @ts-ignore, // @ts-expect-error will alert you when the underlying issue is resolved, preventing stale suppression comments from accumulating.
Testing with Strict Types#
Strict mode changes how you write tests. Mock objects must satisfy the full interface, and spy types must align with the methods they replace.
const mockService = {
getArticles: jasmine.createSpy('getArticles').and.returnValue(of([])),
} as unknown as ArticleService;
The as unknown as double cast is a safe escape hatch when mocking complex services in strict mode.
Enabling TypeScript strict mode takes effort, but the payoff is a codebase that is dramatically easier to refactor, safer to deploy, and more pleasant to maintain.