Type Guard is simply a technique of checking if a certain property or method exists before we try to use it. To understand why we might need this feature in TypeScript, let’s look at an example,
type Message = string;
function display(message: Message) {
console.log(message.toLowerCase());
}
display("HELLO"); // hello
In the above example, we have a function display()
that takes a single parameter of type Message
. The type of Message
is a string
for now.
Let’s say, the message can be of type string
or a number
.
type Message = string | number;
function display(message: Message) {
console.log(message.toLowerCase()); // ❌ Property 'toLowerCase' does not exist on type 'Message'.
// ❌ Property 'toLowerCase' does not exist on type 'number'.
}
As you can see, TypeScript throws an error, Property 'toLowerCase' does not exist on type 'Message'.Property 'toLowerCase' does not exist on type 'number'.
This is because we are trying to use a method toLowerCase()
which works if the message is string
but it will not work if we pass a number. Since the value of the parameter message is determined at runtime we need a way to implement a type guard. Let’s look at different ways in which we can solve such problems in TypeScript.
Using typeOf
typeOf
is a Javascript operator used for type checking. We can implement type guards in Typescript using typeOf
. Let’s rewrite the same example that we have looked at before using typeOf
.
type Message = string | number;
function display(message: Message) {
if (typeof message === "string") {
console.log(message.toLowerCase());
} else {
console.log(message);
}
}
display("HELLO"); // hello
display(1234); // 1234
As you can see from the above example, using typeOf
operator we ensure the toLowerCase()
code only runs if we pass a string. Otherwise, we just display the number as it is.
Using ‘in’ operator
The typeof
operator comes in handy if we are checking for primitive types. But it will not work for more advanced types like for example a custom type like the one below.
type Dog = {
fetch: boolean,
};
type Cat = {
sleep: boolean,
};
type Animal = Dog | Cat;
function checkFetch(animal: Animal) {
console.log(`Can Fetch? - ${animal.fetch}`); // ❌ Property 'fetch' does not exist on type 'Animal'.
// ❌ Property 'fetch' does not exist on type 'Cat'.
}
In the above example, we have Animal
type which is a union type of Dog
and Cat
. We also have a checkFetch
that displays if an animal passed to it can fetch.
TypeScript cannot determine what type of parameter can get passed to this function, hence, it will complain, Property 'fetch' does not exist on type 'Animal'.Property 'fetch' does not exist on type 'Cat'.
We can use the in
operator available in Javascript to check if a property exists. In this case, to check if fetch
property exists.
type Dog = {
fetch: boolean,
};
type Cat = {
sleep: boolean,
};
type Animal = Dog | Cat;
function checkFetch(animal: Animal) {
if ("fetch" in animal) {
console.log(`Can Fetch? - ${animal.fetch}`);
} else {
console.log("Sorry cannot fetch");
}
}
let dog: Dog = {
fetch: true,
};
let cat: Cat = {
sleep: true,
};
checkFetch(dog); // Can Fetch? - true
checkFetch(cat); // Sorry cannot fetch
Using instanceOf
instanceOf
is a Javascript operator that is used to check the type of an object. This operator is helpful when you want to check if an object belongs to a particular class.
class Dog {
fetch() {
console.log("I love to fetch");
}
}
class Cat {
sleep() {
console.log("I love to sleep");
}
}
type Animal = Dog | Cat;
function checkFetch(animal: Animal) {
if (animal instanceof Dog) {
animal.fetch();
} else {
console.log("Sorry cannot fetch");
}
}
let dog = new Dog();
let cat = new Cat();
checkFetch(dog); // I love to fetch
checkFetch(cat); // Sorry cannot fetch
In this example, we execute animal.fetch()
code only if animal
is an instance of class Dog
.