Welcome back to the TypeScript Type Guards series! You’re entering the realms of next level knowledge, woohoo!
For reference, the 4 articles in this TypeScript series:
- Understanding TypeScript: typeof Type Guard
- Understanding TypeScript: instanceof Type Guard
- Understanding TypeScript: User defined Type Guards (you’re here!)
- TypeScript’s Literal Type Guards and “in” Operator
Enjoying the series? Come and master the whole TypeScript language with us across 2 courses, beginner concepts and finally advanced. Full of real-world examples, it’ll answer so many questions you have and will have. Click here to check out more of what you’ll learn with us!
Table of contents
User Defined Type Guards mean that the user can declare a particular Type Guard or you can help TypeScript to infer a type when you use, for example, a function.
We will demonstrate this with the following code:
class Song {
constructor(public title: string, public duration: number) {}
}
class Playlist {
constructor(public name: string, public songs: Song[]) {}
}
function getItemName(item: Song | Playlist) {
if(item instanceof Song) {
return item.title;
}
return item.name;
}
const songName = getItemName(new Song('Wonderful Wonderful', 300000));
console.log('Song name:', songName);
const playlistName = getItemName(
new Playlist('The Best Songs', [new Song('The Man', 300000)])
);
console.log('Playlist name:', playlistName);
The instanceof
and typeof
operators aren’t always the best tool to use. In a scenario where you may want to create or break something down into a reusable function, we lose the type inference if we do not actually have it in our item instanceof Song
because TypeScript cannot infer the type further down. There is a way around this.
Free eBook
Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.
- Observables and Async Pipe
- Identity Checking and Performance
- Web Components <ng-template> syntax
- <ng-container> and Observable Composition
- Advanced Rendering Patterns
- Setters and Getters for Styles and Class Bindings
What we are going to do is create a function and we’ll say this is a Song
. If it is a Song
return me the title
, otherwise return me the name
.
function getItemName(item: Song | Playlist) {
if(isSong(item)) {
return item.title;
}
return item.name;
}
If we were to do this we would be getting some errors from TypeScript. One red underline would be under title
and the other under name
. Let’s go an create the function isSong
. We will pass the argument item
of type any
. We are not going to change the behaviour but we are going to say is the item
is an instanceof
Song
.
function isSong(item: any) {
return item instanceof Song;
}
This code would completely work at runtime however at the compile time with TypeScript we might get a boolean back from our getItemName
function on isSong
, which is the inferred type because instanceof
will return true or false. However we don’t get any type information.
Instead of doing something like this:
function getItemName(item: Song | Playlist) {
if(isSong(item)) {
return (item as Song).title;
}
return item.name;
}
We want to let our Union type in the function argument instruct TypeScript of what we are dealing with. This is the crucial part for understanding a user defined type guard.
So in our isSong
function we can provide extra information. We can say that item
is a Song
. That way when this function evaluates to true we are manually defining and we are returning type information to say that if this function successfully returns true
then what we are dealing with is a Song
.
function isSong(item: any): item is Song {
return item instanceof Song;
}
This is what we call a User Defined Type Guard, you can create any kind of type, any kind of check but it’s up to you to make sure that the item instanceof song
, in this case, returns a boolean. If we were to return an object instead we would see an error because we can only use the is
syntax when we are testing something like a boolean.
We need to always return a boolean and you will see the is
syntax whenever something is returning a boolean but it is supplying further type information, which we can then use.
So this is a User Defined Type Guard, you can use them with your own types, the custom types (like we just saw with Song
), if in your application you want to say it’s going to be a string
array you can do exactly that. You have complete flexibility over what types you want to tell TypeScript are coming back.