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
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
Pet, and two classes
Cat which implement the interface
Now, I would like to write a function which accepts both interfaces
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
TypeScript will narrow the given parameter
animal to that specific type within
else blocks. No type casting is needed. The output for classes
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
Pet get the discriminant property
kind and a new property
subkind. The interface
Animal is gone.
As you can see, the classes
Cat get the discriminant property
kind which has to be set in the constructor. This property can only accept the enum value
Cat implement the interface
The updated function
printAnimal examines the property
kind and controls the program flow via the mentioned
switch — case statement.
The output is the same as before.