TypeScript: Typeguards and Type Narrowing.
Tags: typescript, javascript
Published: 2021-06-27 21:22:43

Hi, 
In this post we will be exploring a feature of TypeScript called type guards. To be more precise we will be exploring the typeguard using the `in` operator. 

Note: The `in` operator is javascript feature not TypeScript.

Consider the following scenario: I am writing a todo application. In my todo application I have two types of Todos:
1. Anonymous Todo
2. Validated Todo

`Anonymous` todo have text and id fields. Lets write an interface for it

```
interface AnonymousTodo {
    text: string;
    id: number;
}
```
`Validated` Todo is similar to anonymous todo but with two extra fields `authorName` and `validationDate`. Lets write interface for it too.

```
interface ValidatedTodo {
    text: string;
    id: number;
    authorName: string;
    validationDate: Date;
}
```
so far so good, now lets write a method which will print the todo to console. So if anonymous todo is passed we should prepend anonymous logging text and id, but if ValidatedTodo is passed we should prepend 🔒 before logging the todo details.
```
function printValidation(todo: AnonymousTodo | ValidatedTodo) {
  
}
```
so our function `printValidation` accepts both `AnonymousTodo` and `ValidatedTodo`. But if you try to `console.log(log.authorName)`; you will get the following error:
```
Property 'authorName' does not exist on type 'ValidatedTodo | AnonymousTodo'.
  Property 'authorName' does not exist on type 'AnonymousTodo'.(2339)
```
Lets try to log `id` instead of `authorName`, that works fine. Now lets try to log `text`, yes thats also works fine finally lets try to log `validationDate`. we get similar error as before.

So why is that? This is because TypeScript wants to make sure we only access the properties which are available on both `ValidatedTodo` and `AnonymousTodo`, in our case these common properties are `id` and `text`. But we want to access `authorName` and `validationDate` too. How can we do that? 

This is where Typeguard comes in. We can use the typeguard to narrow the type. So as of now `todo` can be one of the two types. It can be either of type `AnonymousTodo` or `ValidatedTodo`. So we need to narrow it down for TypeScript, so TypeScript will know which type it is and will allow to access the properties available on it instead of allowing us to only access common properties. If it does not make sense do not worry. I have example coming up. Hopefully it will clear things up

There are multiple different type of guards available eg: `instanceof`,`typeof` etc. But in our case as we are using interface we will narrow the type using the `in` operator. The `in` operator is javascript language feature which can be used to check if a property is present in an object.
```
const myObj = {apple: 20};
if ("apple" in myObj) console.log(`We have ${myObj.apple} apples`);
```
So in the above snippet property `apple` is present in the `myObj` so we get true and a message will be logged to console. so lets integrate this in our example. Before we do that lets create objects of both type:
```
const todoWithValidation: ValidatedTodo = { text: "Ping", id: 1, validationDate: new Date(), authorName: "admin" };
const todoWithoutValidation: AnonymousTodo = { text: "Pong", id: 1 };
```
By looking at the both object we can see that `ValidatedTodo` will always have `validationDate` and `authorName`. So we can tell TypeScript to all at these two properties to distinguish between the `ValidatedTodo` and `AnonymousTodo` and we can do that by adding a simple `if` check which checks for these properties using the `in` operator. lets write the code for this.

```
function printValidation(todo: AnonymousTodo | ValidatedTodo) {
    if ("authorName" in todo && "validationDate" in todo) {
        console.log(`🔒 ${todo.authorName}, ${todo.validationDate}, ${todo.text}`);
    } else {
        console.log(`Anonymous ${todo.id}, ${todo.text}`);
    }
}
```
Inside the else block you can only access the properties of `AnonymousTodo` and inside the `if` block you can only access the properties of `ValidatedTodo` and outside of these scope you can only access the common properties.

Bonus:
Instead of the `if ("authorName" in todo && "validationDate" in todo) ` we can also use a type predicate function:

```
function isValidatedTodo(todo: AnonymousTodo | ValidatedTodo): todo is ValidatedTodo {
  return ("authorName" in todo && "validationDate" in todo);
}
```
Notice the return type type of the function. You can find more details on type predicate in the official docs. Thats all for now. If you want to play with code you can access it [here](https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgGpwDbACZ0tgFQHtsjkBvAKGRuUgA8wAuZAZzClAHMBua2nCxABXALYAjaH1rI4wsAAsiUAHJxREFu04he-GgDdMOPMCIgAInk3IrkPgF9KlUJFiIUAQRDmAnqKJhVmJSCn06CEYtDm5pAWwhMUkoR2cYYRAEMDMQZGBWdCxcfBCiAAowEiIWbz8AoNLkAB80Y2KIQiqAShZK0PzWoutO0KoaKAgwYShcsoAiOUVlNQ05vNy+sgAyLeQ5oyHs8zsINdA6btTKdMyj3IAHHTBCkzuyjCIuGp8Qf0DgqrNQavDqlLphGTAGDIeaLJSqdSndbID5cZA7PYHV45E5nXKo8FjGS0BDmVhEDAQAB0qLKAANALwbgCRd5AAEnIqKpcOWiIcABo2RzPlSscUcdZ+YLOQwwA46V04jQHMgIBhWCgicTkKSQOTKTTPvTar96qwpcKcJL2dLIrL5YrkE4nJQdewLqQAOrARQvMXmFi+4aNAC8FAiUT2AAVuHMBYJkABGAWi0zHaxCCAAd1s1jKXQF3IRGhYC2wolAawcfFdYHdRC9S3kgbu3zq-xDYZlJejuljeQSicdfEoj1czZyFSqDYU4-MCpHT1nIEnnu9SibbVTIHnQA).