Understanding TypeScript: typeof Type Guard blog post

Understanding TypeScript: typeof Type Guard

Todd Motto

8 Aug, 2019

TypeScript

8 minutes read

Welcome to this epic TypeScript series on Type Guards. In this short series of 4 articles, we’re going to teach you all about Type Guards – the what, why and how’s so you can uncover some of the more advanced features we’d love to teach you.

For reference, the 4 articles in this TypeScript series:

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!

Here’s the source code of what we’re going to learn today:

So, before we begin – what is a "Type Guard" in TypeScript?

A Type Guard is a way that we can get hold of type information after making a check inside a conditional such as an if statement, an else if or an else. The main idea is that when we use a Type Guard we essentially get information and we get the type information given to us inside that conditional block.

"typeof" Type Guard

We call it the typeof Type Guard because we are using the typeof operator in JavaScript. TypeScript is aware that we can use the typeof operator much like the instanceof as well.

What we want to do before we get started is understand how we can use the typeof operator inside perhaps a function and then we get given the type information if, for example, our conditional was true in a particular scenario.

To demonstrate this what we’re going to do is explain this with a function that doesn’t really mean anything, we’re just simply demonstrating the example.

function foo(bar: string | number) {
  if(typeof bar === 'string') {
    //string
  }
  //number
}

So in the function below we can see that bar can be of type string or number. Inside our if statement we are saying that bar is of type string value which means that after our if statement TypeScript cleverly knows that we are using a number.

Let’s demonstrate this. Inside our if statement we can say bar.

function foo(bar: string | number) {
  if(typeof bar === 'string') {
    bar.
  }
  //number
}

We will be able to access String.anchor, big, blink and these are all string prototype methods and they exist only on the string objects. We also have access to slice, replace and things that you’ll commonly be using with JavaScript and TypeScript.

If we now go ahead and remove this and further down where TypeScript cleverly knows that we are using a number add bar and hover over this TypeScript will tell us that bar: string | number.

function foo(bar: string | number) {
  if(typeof bar === 'string') {
    //string
  }
  bar
}

So it might be that we want to return bar to uppercase. Which would mean that our bar below resulted in just being a number because we are returning at this point a string method. TypeScript is detecting what we can do with this information.

function foo(bar: string | number) {
  if(typeof bar === 'string') {
    return bar.toUpperCase();
  }
  //number
  bar
}

If we add the dot to bar. we would now see the number methods all available to us on the prototype.

function foo(bar: string | number) {
  if(typeof bar === 'string') {
    return bar.toUpperCase();
  }
  //number
  bar.
}

It might be that we want to take this if statement and so something similar below with a number. So we could say, if(typeof bar === 'number') and then have a return statement.

function foo(bar: string | number) {
  if(typeof bar === 'string') {
    return bar.toUpperCase();
  }
  if(typeof bar === 'number') {
    return bar.toFixed(2);
  }
}

We could completely safety check all of these, is it a string?, is it definitely a number? or we could let TypeScript work this out for itself as we have just seen.

Understanding Type Guards

Now we are going to build out a better example for this. This time with a class.

class Song {
  constructor(public title: string, public duration: string | number) {}
}

We have two options and we’re going to create our Type Guard to detect whether the duration is going to be a string or a number. If it’s a number we are going to parse that and then get the actual value back otherwise we are going to return a string.

First of all we will setup our function which will take in an item of type Song. We will use this function twice to understand what the Type Guard can do for us. Our const songDurationFromString will have a string to the song and then will get that value given back.

class Song {
  constructor(public title: string, public duration: string | number) {}
}

function getSongDuration(item: Song) {

}

const songDurationFromString = getSongDuration(
  new Song('Wonderful Wonderful', '05:31') 
);

It might be that in our application we are dealing with different number formats, we might get given the milliseconds or we might get given the string value representation passed out for us. It might be that we get this from the DOM, from the database or other areas in our application.

So this makes sense as a nice utility function to get us the song duration whether it’s a string and it’s already been passed out or it’s just the milliseconds.

If we log this out console.log(songDurationFromString); it won’t do anything so we need to implement our Type Guard. First of all if our item.duration is already a string we just simply want to return it.

class Song {
  constructor(public title: string, public duration: string | number) {}
}

function getSongDuration(item: Song) {
  if (typeof item.duration === 'string') {
    return item.duration;
  }
}

const songDurationFromString = getSongDuration(
  new Song('Wonderful Wonderful', '05:31') 
);
console.log(songDurationFromString);

Otherwise we are then going to convert those milliseconds to look like a string. Above we mentioned that we would use the function twice to get a better understanding of it. So let’s do that. We are going to say get the song duration from milliseconds songDurationFromMS. We will then instead of passing a string as the second argument, pass a millisecond date-stamp.

class Song {
  constructor(public title: string, public duration: string | number) {}
}

function getSongDuration(item: Song) {
  if (typeof item.duration === 'string') {
    return item.duration;
  }
}

const songDurationFromString = getSongDuration(
  new Song('Wonderful Wonderful', '05:31') 
);
console.log(songDurationFromString);


const songDurationFromMS = getSongDuration(
  new Song('Wonderful Wonderful', 330000) 
);

The next step is what do we do once we failed the first Guard:

if (typeof item.duration === 'string') {
    return item.duration;
  }

So if we are not dealing with a string, then it must be that we are dealing with a number. To double check this we can add return item.duration and hover over it and TypeScript will tell us that Song.duration: number. So we can see that we are definitely dealing with a number.

function getSongDuration(item: Song) {
  if (typeof item.duration === 'string') {
    return item.duration;
  }
  return item.duration;
}

Instead of returning what we are going to do is create a few constants. Firstly, we will de-structure the duration from the item. We’re then going to obtain the minutes se we can do const minutes = Math.floor(duration / 60000);. This will give us the minutes based on the time stamp that we passed in. Next we need to deal with the seconds const seconds = Math.floor(duration / 1000) % 60;. Then we need to compose that return string. So we can say return ${minutes}:${seconds};

function getSongDuration(item: Song) {
  if (typeof item.duration === 'string') {
    return item.duration;
  }
  const { duration } = item;
  const minutes = Math.floor(duration / 60000);
  const seconds = (duration / 1000) % 60;
  return `${minutes}:${seconds}`;
}

Either way we are returning a string from this function but the way that our Type Guard is dealing with this if the duration is a string to begin with, we just simply assume that we have already passed it out and we just return it. Otherwise we are going to make a few calculations and get those minutes and seconds.

Now let’s log all of this out.

class Song {
  constructor(public title: string, public duration: string | number) {}
}

function getSongDuration(item: Song) {
  if (typeof item.duration === 'string') {
    return item.duration;
  }
  const { duration } = item;
  const minutes = Math.floor(duration / 60000);
  const seconds = (duration / 1000) % 60;
  return `${minutes}:${seconds}`;
}

const songDurationFromString = getSongDuration(
  new Song('Wonderful Wonderful', '05:31') 
);
//05:31
console.log(songDurationFromString);


const songDurationFromMS = getSongDuration(
  new Song('Wonderful Wonderful', 330000) 
);
//5:30
console.log(songDurationFromMS);

Here’s the source code running, check the console!

This post is to demonstrate the power of using a Type Guard with TypeScript. By using the typeof operator TypeScript knows that once that check has successfully been passed that we are dealing with a, in this case, string inside of our if statement.

That is what TypeScript is saying too and it is clever enough to actually detect that if we’re not dealing with a string, we are dealing with our other alternative. That type information is indeed coming from our public property of duration, so the type information is flowing down into the function and we could wrap the second part of our function into another if statement just to double check the item.duration is a number but because TypeScript is helping us out here we can be sure that we are in fact dealing with a number type.

About the author

Todd Motto profile picture

Todd Motto

GDE Google Developer Expert

Todd is the Founder of Ultimate Courses. With a passion for Angular, TypeScript and JavaScript, Todd leads the online courses creation and has written hundreds of articles on front-end web development and beyond. He specialises in breaking down complex topics and understands the critical mission of learning new technology fast, comprehensively and the right way.

Love the post? Share it!

Lots of time and effort go into all our blogs, resources and demos,
we'd love it if you'd spare a moment to share them!

Explore our TypeScript courses

Get started today and join over 50,000 developers.