Module lessons (1/2)
Type Guards
Type narrowing is one of the most important concepts in TypeScript. Often, however, standard type guards like typeof or instanceof are not sufficient for complex custom objects. This is where Custom Type Guards come in.
Standard Type Guards
Let's begin by summarizing how TypeScript narrows types using standard JavaScript operators:
function processInput(val: string | number) {
if (typeof val === 'string') {
// Here 'val' is of type string
console.log(val.toUpperCase());
} else {
// Here 'val' is of type number
console.log(val.toFixed(2));
}
}Custom Type Guards (is operator)
To define a custom type guard, we create a function that returns a type predicate in the format parameterName is Type instead of a standard boolean return type.
The function must return a boolean (true or false). If it returns true, TypeScript will know that the passed parameter is of the specified type.
interface Cat {
name: string;
meow(): void;
}
interface Dog {
name: string;
bark(): void;
}
// This is a custom type guard
function isCat(animal: Cat | Dog): animal is Cat {
return (animal as Cat).meow !== undefined;
}
function makeNoise(pet: Cat | Dog) {
if (isCat(pet)) {
// Here TypeScript knows 'pet' is a Cat
pet.meow();
} else {
// Here TypeScript knows 'pet' is a Dog
pet.bark();
}
}Safety and Casting (as vs Type Guards)
Type casting (e.g., val as Cat) forces the compiler to trust the developer without any real runtime verification. If the runtime object does not meet expectations, the code will fail silently or throw exceptions.
Custom type guards, on the other hand, perform a robust and dynamic runtime check, safely and accurately informing the TypeScript compiler of the actual variable type:
function processInput(input: unknown) {
// unsafe casting: could crash if input does not have the split method
// const str = input as string;
// console.log(str.split(' '));
// safe guard
if (isString(input)) {
console.log(input.split(' ')); // 100% Safe!
}
}Try it yourself
Exercise 1: String Type Guard
Create a simple function named isString that acts as a custom type guard to verify if an unknown value is a string.
Show hint
The return type of the function must be val is string, and you should use typeof to check if it equals 'string'.
Solution available after 3 attempts
Exercise 2: Premium User Type Guard
Given the User and PremiumUser types, write a type guard function isPremiumUser(user: User): user is PremiumUser that checks if the user has the 'premium' role.
Show hint
Define the signature as function isPremiumUser(user: User): user is PremiumUser and compare user.role with 'premium'.
Solution available after 3 attempts
Exercise 3: Custom Error Object Validation
Implement the isCustomError(err: unknown): err is CustomError function that verifies if an unknown value is a non-null CustomError object containing the 'code' key.
Show hint
First check if typeof err is 'object' and not null, then use the 'in' operator to check for the 'code' property.
Solution available after 3 attempts
Exercise 4: Admin Type Guard
Given two interfaces User (with property role: string) and Admin (extending User and having a property adminToken: string), write a type guard function named isAdmin that accepts a user of type User and returns a type predicate user is Admin. The guard must check if the user's role is equal to 'admin'.
Show hint
Define the signature as function isAdmin(user: User): user is Admin and check if user.role === 'admin'.
Solution available after 3 attempts