Narrowing Types in Typescript Using Type Guards

A Type Guard is a technique where Typescript gets type information of a variable after making a type check using a conditional such as an if statement (or switch statement), an else if or an else.

Why Type Guards

Consider the following code. The formatAmount accepts a union type number | string as its argument. Inside the method, we use the parseInt to convert the string to number The parseInt accepts only string. Since it may be possible that the user might send a number rather than a string, the TypeScript compiler rightly flags this code as an error.

We can use any like parseInt(money as any). But using any defeats the very purpose of using the TypeScript.

The solution is to give the TypeScript compiler a hint about the type so that the parseInt(money) does not throw errors. The solution must not break the Type system like using the any type. This is where the Type Guards comes in.

What is a Type Guard

The document describes the Type Guard as an expression that performs a runtime check that guarantees the type in some scope. This definition has two parts

  1. Performs a runtime check on the type
  2. Guarantees the type in the scope of the above check

There are five ways, by which we can Perform a runtime check.

Typeof Type Guard

A typeOf keyword returns the type of an identifier in TypeScript. We can use that to check the Type of money variable. If it is a string, then we can proceed with the use of parseInt. The code is as shown below.

Now, the error magically disappears. The if (typeof money == "string") block acts as Type Guard, hinting to the compiler that the inside the if block the money is string. Hence the money is treated as a string in the if block. As number in the else block. Outside the if.. else block it is treated as number | string.

You can see verify it by hovering over the money variable as shown in the image below.

But the typeof can only detect primitives types like numberstringbooleansymbolundefinedfunction. For everything else, it returns object.

InstanceOf Type Guard

InstanceOf checks if a value is an instance of a class or a constructor function. The syntax for using it is as follows.

It returns true if the obj is an instance of the class, or it appears anywhere in the inheritance chain. Else it will return false.

In the following code both Customer & SalesPerson extend the Person class. They have a common method code().They also have additional methods buy() (Customer class) and sell() (SalesPerson class).

The method getCode get its argument as of type Person as we want to use it for both Customer & SalesPerson. The obj.code() method does not compile as the method code does not exist on the class Person. But we know that both inherited classes Customer & SalesPerson implement it.

We cannot use typeof here as it returns object. But we can use the InstanceOf type guard.

We check the type of obj using obj instanceof Customer. If it is true then the TypeScript treats the obj as Customer. We use the same technique in the else block, where it is obj is treated as SalesPerson.

You can verify it by checking the intellisense. Inside the if (obj instanceOf customer) block, the typescript correctly infers the type as Customer, It shows the method Buy But does not show the method Sell

While inside the if (obj instanceOf SalesPerson) block it infers the type as SalesPerson. Here it shows the method Sell and does not show Buy

Outside both the blocks, the intellisense shows only one property name, which is from the Person class

TypeScript InstanceOf Type Guard
TypeScript InstanceOf Type Guard

In Operator

The in operator does not check the type, but it checks if a property exists on an object. The syntax is

The right hand of the expression must be an object.

The car in the following code has a property start. We can use the in operator to check for it using the 'start' in car. It returns true.

The in operator also works as a Type guard. In the getCode method below, we check if the buy & sell methods exist in the obj. Inside the if block, the compiler correctly infers the type as customer & salesPerson. Remember to use the unique property while using the in. If you use the code property, which exists in both, the compiler will throw the error as it exists in both the classes.

Custom Type Guard / Type Predicates

Type Predicates allow us to specify our own custom logic or user defined Type guards. To define a custom type guard, we need to write a function whose return type is a type predicate.

A type predicate looks like  propertyName is Type. Where propertyName (Must be the name of the function parameter) is of type Type. If the function returns true, it indicates that propertyName is Type is true.

In the following IsCustomer checks if the parameter obj is of type customer. Hence we define the return type as obj is Customer. Our custom logic checks if the property buy exists in the obj. If yes then we return true.

Once our custom type guard is ready, we can use it similar to other Type guards as shown below.

Discriminated Unions

The Discriminated Unions is another way by which you can take advantage of the Type Guards. It is actually a pattern consisting of a common literal type property (Discriminant Property), Union types, Type aliases & Type guards.

You can read more on Discriminated Unions

References

Advanced Types

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Scroll to Top