Summary: In this tutorial, you will learn about Type Guards in TypeScript to narrow down the type of a variable.
Type Guards allow you to narrow down the type of a variable within a conditional block.
The typeof operator #
Let’s take a look at the following example:
type alphanumeric = string | number;
function add(a: alphanumeric, b: alphanumeric) {
if (typeof a === 'number' && typeof b === 'number') {
return a + b;
}
if (typeof a === 'string' && typeof b === 'string') {
return a.concat(b);
}
throw new Error('Invalid arguments. Both arguments must be either numbers or strings.');
}
How it works:
- First, define the
alphanumerictype that can hold either a string or a number. - Next, declare a function that adds two variables
aandbwith the type ofalphanumeric. - Then, check if both types of arguments are numbers using the
typeofoperator. If yes, then calculate the sum of the arguments using the+operator. - After that, check if both types of arguments are strings using the
typeofoperator. If yes, then concatenate the two arguments. - Finally, throw an error if arguments are neither numbers nor strings.
In this example, TypeScript knows the usage of the typeof operator in the conditional blocks. Inside the following if block, TypeScript realizes that a and b are numbers.
if (typeof a === 'number' && typeof b === 'number') {
return a + b;
}
Similarly, in the following if block, TypeScript treats a and b as strings; therefore, you can concatenate them into one:
if (typeof a === 'string' && typeof b === 'string') {
return a.concat(b);
}
The instanceof operator #
Similar to the typeof operator, TypeScript is also aware of the usage of the instanceof operator. For example:
class Customer {
isCreditAllowed(): boolean {
// ...
return true;
}
}
class Supplier {
isInShortList(): boolean {
// ...
return true;
}
}
type BusinessPartner = Customer | Supplier;
function signContract(partner: BusinessPartner) : string {
let message: string;
if (partner instanceof Customer) {
message = partner.isCreditAllowed() ? 'Sign a new contract with the customer' : 'Credit issue';
}
if (partner instanceof Supplier) {
message = partner.isInShortList() ? 'Sign a new contract the supplier' : 'Need to evaluate further';
}
return message;
}
How it works:
- First, declare the
CustomerandSupplierclasses. - Second, create a type alias
BusinessPartnerwhich is a union type ofCustomerandSupplier. - Third, declare a function
signContract()that accepts a parameter with the typeBusinessPartner. - Finally, check if the partner is an instance of
CustomerorSupplier, and then provide the respective logic.
Inside the following if block, TypeScript knows that the partner is an instance of the Customer type due to the instanceof operator:
if (partner instanceof Customer) {
message = partner.isCreditAllowed() ? 'Sign a new contract with the customer' : 'Credit issue';
}
Likewise, TypeScript knows that the partner is an instance of Supplier inside the following if block:
if (partner instanceof Supplier) {
message = partner.isInShortList() ? 'Sign a new contract with the supplier' : 'Need to evaluate further';
}
When an if narrows out one type, TypeScript knows that within the else it is not that type but the other. For example:
function signContract(partner: BusinessPartner) : string {
let message: string;
if (partner instanceof Customer) {
message = partner.isCreditAllowed() ? 'Sign a new contract with the customer' : 'Credit issue';
} else {
// must be Supplier
message = partner.isInShortList() ? 'Sign a new contract with the supplier' : 'Need to evaluate further';
}
return message;
}
The in operator #
The in operator carries a safe check for the existence of a property on an object. You can also use it as a type guard. For example:
function signContract(partner: BusinessPartner) : string {
let message: string;
if ('isCreditAllowed' in partner) {
message = partner.isCreditAllowed() ? 'Sign a new contract with the customer' : 'Credit issue';
} else {
// must be Supplier
message = partner.isInShortList() ? 'Sign a new contract the supplier ' : 'Need to evaluate further';
}
return message;
}
User-defined Type Guards #
When the type-narrowing logic is complex or needs to be reused in multiple places, you can move the check into a user-defined type guard function.
A user-defined type guard uses a special return type called a type predicate.
Here is the type predicate syntax:
parameterName is Type
For example:
function isCustomer(partner: any): partner is Customer {
return partner instanceof Customer;
}
In this function, the type predicate is:
partner is Customer
It tells TypeScript that if isCustomer(partner) returns true, treat partner as a Customer. The name before is must match the name of a function parameter.
The partner is Customer annotation provides additional information to the TypeScript compiler so that it can narrow the type in the calling code.
You can use the type guard in an if statement:
function signContract(partner: BusinessPartner): string {
let message: string;
if (isCustomer(partner)) {
message = partner.isCreditAllowed() ? 'Sign a new contract with the customer' : 'Credit issue';
} else {
message = partner.isInShortList() ? 'Sign a new contract with the supplier' : 'Need to evaluate further';
}
return message;
}
Inside this block:
if (isCustomer(partner)) {
// partner is Customer
}
TypeScript narrows partner from:
Customer | Supplier
to:
Customer
Therefore, you can safely call the Customer method:
partner.isCreditAllowed();
In the else branch, TypeScript knows that partner must be a Supplier:
else {
// partner is Supplier
partner.isInShortList();
}
Type Predicate vs. Boolean Return Type #
The following isCustomer function returns true if a partner is a customer:
function isCustomer(partner: BusinessPartner): boolean {
return partner instanceof Customer;
}
The function answers the runtime question, but its return type (boolean) does not explicitly describe the intended narrowing relationship.
A type predicate communicates that relationship:
function isCustomer(
partner: BusinessPartner
): partner is Customer {
return partner instanceof Customer;
}
Therefore:
booleanmeans the function returnstrueorfalse.partner is Customermeans the function returns a boolean and, when the result istrue,partnercan be treated as aCustomer.
Summary #
- Type guards narrow down the type of a variable within a conditional block.
- Use the
typeofandinstanceofoperators to implement type guards in conditional blocks
Thank you for your feedback!