Close

TypeScript - Type Guards

[Updated: Oct 28, 2018, Created: Oct 26, 2018]

In the last tutorial we saw union types which can be used to provide multiple type choices for a variable.

We use type guards to differentiate between the possible value of the union types. A type guard is nothing but a boolean expression; if it is true for a target type, the corresponding block gets executed where we apply a desired logic on that type.

Type guards for primitives

The JavaScript typeof operator can be used as type guards for primitives. The operator typeof returns a string indicating the underlying type. This string value must be "number", "string", "boolean", or "symbol".

Example:

function show(x: number | string): void {
    console.log("typeof x: " + typeof x);
    if (typeof x == 'number') {
        console.log("a number: " + x);
    } else {
        console.log("a string: " + x);
    }
}

show("test string");
show(4);

Output

typeof x: string
a string: test string
typeof x: number
a number: 4

Type guards for objects

The typeof operator for an object always returns 'object'.

class Person {}
let person = new Person();
console.log(typeof person);
console.log(typeof new String("test"));
console.log(typeof null);

Output

object
object
object

So how to use type guards for objects? In JavaScript, we typically decide on the object types by checking whether their particular properties/methods are defined or not.

Let's consider following example:

type-guards-example.ts

class Car {
    start() {
        console.log("car starting");
    }

    drive() {
        console.log("car driving");
    }
}

class Bike {
    start() {
        console.log("bike starting");
    }

    ride() {
        console.log("Bike ridding");
    }
}

function move(vehicle: Bike | Car): void {
    vehicle.start();
    if (vehicle.drive) { //same as if(vehicle.drive!=undefined)
        vehicle.drive();
    } else {
        vehicle.ride();
    }
}

move(new Bike());

Output

error TS5033: Could not write file 'type-guards-example.js': EPERM: operation not permitted, open 'D:\example-projects\typescript\advance-types\typescript-type-guards\type-guards-example.js'.
type-guards-example.ts(23,17): error TS2339: Property 'drive' does not exist on type 'Car | Bike'.
Property 'drive' does not exist on type 'Bike'.
type-guards-example.ts(24,17): error TS2339: Property 'drive' does not exist on type 'Car | Bike'.
Property 'drive' does not exist on type 'Bike'.
type-guards-example.ts(26,17): error TS2339: Property 'ride' does not exist on type 'Car | Bike'.
Property 'ride' does not exist on type 'Car'.

TypeScript only allows access to the members that are guaranteed to be in all the constituents of a union type.
To tell TypeScript the types within guards, we need to use type assertion:

type-guards-example2.ts

class Car {
    drive() {
        console.log("car driving");
    }
}

class Bike {
    ride() {
        console.log("Bike ridding");
    }
}

function move(vehicle: Bike | Car): void {
    if ((vehicle as Car).drive) {
        (vehicle as Car).drive();
    } else {
        (vehicle as Bike).ride();
    }
}

move(new Bike());

Output

Bike ridding

User-defined type guard

To avoid several type assertions (specially when there are multiple if/else paths), TypeScript allows us to define our own function which should return a type predicate.

User-defined type guards for objects

Let's rewrite our above example:

type-guards-example3.ts

class Car {
    drive() {
        console.log("car driving");
    }
}

class Bike {
    ride() {
        console.log("Bike ridding");
    }
}

function isCar(vehicle: Bike | Car): vehicle is Car {
    return (vehicle as Car).drive != undefined;
}

function move(vehicle: Bike | Car): void {
    if (isCar(vehicle)) {
        vehicle.drive();
    } else {
        vehicle.ride();
    }
}

move(new Bike());

Output

Bike ridding

In function isCar(), vehicle is Car is our type predicate.
Type predicate's general syntax: parameterName is Type
where parameterName must be the name of a parameter from the current function signature.

Why do we need this new syntax? Can we just return 'boolean' from our isCar() function. The answer is; boolean returned value is only known during the runtime, but the type vehicle is Car (in our example) is a compile time assertion. It is just like a typeof operator that works for objects as well. Let's rewrite our example returning boolean to see what will happen:

type-guards-example4.ts

class Car {
    drive() {
        console.log("car driving");
    }
}

class Bike {
    ride() {
        console.log("Bike ridding");
    }
}

function isCar(vehicle: Bike | Car): boolean {
    return (vehicle as Car).drive != undefined;
}

function move(vehicle: Bike | Car): void {
    if (isCar(vehicle)) {
        vehicle.drive();
    } else {
        vehicle.ride();
    }
}

move(new Bike());

Output

error TS5033: Could not write file 'type-guards-example4.js': EPERM: operation not permitted, open 'D:\example-projects\typescript\advance-types\typescript-type-guards\type-guards-example4.js'.
type-guards-example4.ts(19,17): error TS2339: Property 'drive' does not exist on type 'Car | Bike'.
Property 'drive' does not exist on type 'Bike'.
type-guards-example4.ts(21,17): error TS2339: Property 'ride' does not exist on type 'Car | Bike'.
Property 'ride' does not exist on type 'Car'.

User-defined type guards for primitives

type-of-guards-example2.ts

function isNumber(x: any): x is number {
    return typeof x == 'number';
}

function show(x: number | string): void {
    if (isNumber(x)) {
        console.log("number value: " + x);
    } else {
        console.log("string value: " + x);
    }
}

show("hello");
show(4);

Output

string value: hello
number value: 4

instanceof type guards

TypeScript works well with JavaScript's instanceof operator as a type guard (without type assertions or user-defined type guard functions):

instance-of-guards-example.ts

class Car {
    drive() {
        console.log("car driving");
    }
}

class Bike {
    ride() {
        console.log("Bike ridding");
    }
}

function move(vehicle: Bike | Car): void {
    if (vehicle instanceof Car) {
        vehicle.drive();//no compile time or runtime errors
    } else {
        vehicle.ride();
    }
}

move(new Bike());

Output

Bike ridding

Example Project

Dependencies and Technologies Used:

  • TypeScript 3.1.3
User defined Type Guards Select All Download
  • typescript-type-guards
    • type-guards-example2.ts

    See Also