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 (you’re here!)
- Understanding TypeScript: User defined Type Guards
- 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!
Before we dive into what the instanceof
Type Guard can do for us we are going to do a quick demonstration what instanceof
on its own does for us.
Table of contents
instanceof
For this what we are going to use is just a simple class and then we’ll move along to a more real-world demonstration. We create a class called Foo
and we pass it an empty bar()
method.
class Foo {
bar() {}
}
What is important to understand is that this will compile down to es5 which we will see something like this:
function Foo() {}
Foo.prototype.bar = function () {};
This is how we can create our own custom objects in JavaScript and how we can extend the prototype chain so that when we create new instances they all inherit the same methods. It’s the prototype
piece which is important because all the classes are sugar syntax essentially for this.
So let’s continue and say const
and call it bar
and then create a new instance of our Foo()
class.
class Foo {
bar() {}
}
const bar = new Foo()
The interesting part and what this instanceof
operator will do for us by itself, without Type Guards, is that it tests whether the prototype property of a constructor exists somewhere in another object.
One way we could test this is saying Object.getPrototypeOf()
and because bar
and the value of bar is in fact an instance of Foo()
we want to ask for the prototype of bar
. We can say if it equals Foo.prototype
then essentially bar is going to be an instance of Foo
. Where we access the .prototype
we’re not creating a new instance of Foo
we are just referencing that prototype object.
class Foo {
bar() {}
}
const bar = new Foo();
//true
console.log(Object.getPrototypeOf(bar) === Foo);
You can see that if we log this out we get a true
value and we’re essentially seeing if bar
is an instance of Foo
. Which in this case it’s telling us that it is. Now we do have the ability to use bar
and the instanceof
Foo
.
class Foo {
bar() {}
}
const bar = new Foo();
// true
console.log(bar instanceof Foo);
// true
console.log(Object.getPrototypeOf(bar) === Foo);
As you can see there is no difference between the two. Which means that we can either use the getPrototypeOf
or what we really want to use is the instanceof
keyword. Now we know what the instanceof
does we can move down and implement our own example and we’ll demonstrate this with a Type Guard.
## instance and Type Guards ##
We want a class of Song, we are going to create another constructor it will have a public title
property of type string, a public duration
of type number. We are not going a union type in this video we are going to keep that duration as a number.
const bar = new Foo();
class Song {
constructor(public title: string, public duration: number) {}
}
Now we want to create a playlist class, inside of there we are going to create a constructor with a public name
property of type string and a public property called songs
and this will hold an array of our Song
.
const bar = new Foo();
class Song {
constructor(public title: string, public duration: number) {}
}
class Playlist {
constructor(public name: string, public songs: Song[]) {}
}
What we are interested in doing is whether we pass it a Song
or whether we pass it a Playlist
we just want to know what it’s called, we want the name of it. To do this we will setup a function called getItemName
which will take an item or either type Song
or Playlist
.
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
We might have to manually check whether item
in fact has a title property. So we might actually say if item, and we use that type assertion to say at this moment in time I’m treating item
as Song
so I can successfully lookup that title property.
const bar = new Foo();
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 as Song).title) {
}
}
Inside our if statement we are now assuming that we are dealing with a Song
. The problem is that TypeScript doesn’t know this. So what we need to do is specify it again and then we get access to the autocompletion. Now this part of our code is safety checked.
const bar = new Foo();
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 as Song).title) {
return (item as Song).title;
}
}
Underneath we would have to do the exact same for the Playlist.
const bar = new Foo();
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 as Song).title) {
return (item as Song).title;
}
return (item as Playlist).name;
}
This means that we are definitely returning a string from the function because our title
is a string and the name
is also a string. Now let’s go ahead and say we want to access the song name and we’re going to invoke our getItemName
function. We can pass in a new song and we can give it some digits.
const bar = new Foo();
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 as Song).title) {
return (item as Song).title;
}
return (item as Playlist).name;
}
const songName = getItemName(new Song('Wonderful Wonderful', 300000));
console.log('Song name:', songName);
Next we can access the playlist name. We are going to create a new instance of our playlist and we can supply the name of the Playlist and an array of songs.
const bar = new Foo();
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 as Song).title) {
return (item as Song).title;
}
return (item as Playlist).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);
Now if we log this out we would get:
Song name: Wonderful, Wonderful
Playlist name: The Best Songs
So this code is working as we expect it to. However, we don’t want to keep littering our code with all this information. This doesn’t just apply to the instanceof
operator, it also applies to the other Type Guards in TypeScript.
What we’re going to do is clean up our function getItemName
. If we say that our item
is an instance of Song
then we definitely know it’s a Song
. TypeScript can infer this information for us which means we can remove our type assertion. Similarly we can go ahead and do the same for the playlist.
function getItemName(item: Song | Playlist) {
if(item instanceof Song) {
return item.title;
}
return item.name;
}
What we have done is refactored our code to not use type assertions but to allow us to use that instanceof
operator to infer the type. It’s going to check whether the item
is an instance of the song which is just a static class and if it is it’s going to infer those types for us. This doesn’t mean that we cannot add another if statement and do the same for Playlist
as we did for Song
. But we can also leave it as it is.
That’s an overview of the instanceof
, how it works, the prototype and how we can use them to infer types with our Type Guards.