Narrow interfaces in TypeScript

In this post, I’m going to discuss how to use TypeScript’s type guards to narrow interfaces. If you have an union type as method parameter, for instance A | B where A and B are interfaces, you probably want to avoid ugly type assertions (type casting) within the method to access specific interface properties. Type assertions could look as follows:

What is the best way to write the code without type assertions? Let’s design a concret model. The model consists of the interface Animal, two extended interfaces WildAnimal, Pet, and two classes Dog and Cat which implement the interface Pet.

Now, I would like to write a function which accepts both interfaces WildAnimal and Pet. This function should print the common property kind and the specific properties for each interface. If we have Pet, the property name should be printed. Otherwise, the property livingArea. How to check the given interface? One possibility is so called user-defined type guards. A such type guard is a function with the return type as type predicate. Let’s name this function isPet.

TypeScript will narrow the given parameter animal to that specific type within if and else blocks. No type casting is needed. The output for classes Dog and Cat looks like as follows:

This is fine, but I personally don’t like to write an extra function as type guard. It’s matter of taste, but the if-check doesn’t look good for me. What is about a simpler check with instanceof type guard? The instanceof operator can be used in TypeScript as a type guard too. It allows to narrow types, but not for interfaces! If I would write animal instanceof Pet, the TypeScript playground would show an error — cannot find name ‘Pet’.

The error message is correct. The name Pet is not defined in the variable declaration space. In other words, interfaces don’t exist in the transpiled code. Look at the ES5 code TypeScript produces.

Fortunately, TypeScript 2.0 has introduced a new feature called discriminated unions. We can define a common property in every interface which can be checked in the switch — case statement. The common property is called discriminant. The switch — case statement acts as type guard and make the type casting unnecessary.

A new model has now enum AnimalKind whose values are used for the discriminant (enums are less error-prone than hard-coded strings). Both interfaces WildAnimal and Pet get the discriminant property kind and a new property subkind. The interface Animal is gone.

As you can see, the classes Dog and Cat get the discriminant property kind which has to be set in the constructor. This property can only accept the enum value AnimalKind.PET because Dog and Cat implement the interface Pet.

The updated function printAnimal examines the property kind and controls the program flow via the mentionedswitch — case statement.

The output is the same as before.

Like what you read? Give Oleg Varaksin a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.