# TypeScript Type Guards | webally.co.za

This page lists some of the more advanced ways in which you can model types, it works in tandem with the Utility Types doc which includes types which are included in TypeScript and available globally.

# Type Guards and Differentiating Types

Union types are useful for modeling situations when values can overlap in the types they can take on. What happens when we need to know specifically whether we have a Fish? A common idiom in JavaScript to differentiate between two possible values is to check for the presence of a member. As we mentioned, you can only access members that are guaranteed to be in all the constituents of a union type.

let pet = getSmallPet();
 
// You can use the 'in' operator to check
if ("swim" in pet) {
  pet.swim();
}
// However, you cannot use property access
if (pet.fly) {
Property 'fly' does not exist on type 'Fish | Bird'.
  Property 'fly' does not exist on type 'Fish'.
  pet.fly();
Property 'fly' does not exist on type 'Fish | Bird'.
  Property 'fly' does not exist on type 'Fish'.
}

To get the same code working via property accessors, we’ll need to use a type assertion:

# Type predicates

let pet = getSmallPet();
let fishPet = pet as Fish;
let birdPet = pet as Bird;
 
if (fishPet.swim) {
  fishPet.swim();
} else if (birdPet.fly) {
  birdPet.fly();
}

# User-Defined Type Guards

This isn’t the sort of code you would want in your codebase however.

It would be much better if once we performed the check, we could know the type of pet within each branch.

It just so happens that TypeScript has something called a type guard. A type guard is some expression that performs a runtime check that guarantees the type in some scope.

# Using type predicates

function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

pet is Fish is our type predicate in this example. A predicate takes the form parameterName is Type, where parameterName must be the name of a parameter from the current function signature.

Any time isFish is called with some variable, TypeScript will narrow that variable to that specific type if the original type is compatible.

// Both calls to 'swim' and 'fly' are now okay.
let pet = getSmallPet();
 
if (isFish(pet)) {
  pet.swim();
} else {
  pet.fly();
}

Notice that TypeScript not only knows that pet is a Fish in the if branch; it also knows that in the else branch, you don’t have a Fish, so you must have a Bird.

You may use the type guard isFish to filter an array of Fish | Bird and obtain an array of Fish:

const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()];
const underWater1: Fish[] = zoo.filter(isFish);
// or, equivalently
const underWater2: Fish[] = zoo.filter<Fish>(isFish);
const underWater3: Fish[] = zoo.filter<Fish>((pet) => isFish(pet));
Argument of type '(pet: Fish | Bird) => boolean' is not assignable to parameter of type '(value: Fish | Bird, index: number, array: (Fish | Bird)[]) => value is Fish'.
  Signature '(pet: Fish | Bird): boolean' must be a type predicate.

# Using the in operator

The in operator also acts as a narrowing expression for types.

For a n in x expression, where n is a string literal or string literal type and x is a union type, the “true” branch narrows to types which have an optional or required property n, and the “false” branch narrows to types which have an optional or missing property n.

function move(pet: Fish | Bird) {
  if ("swim" in pet) {
    return pet.swim();
  }
  return pet.fly();
}

# typeof type guards

Let’s go back and write the code for a version of padLeft which uses union types. We could write it with type predicates as follows:

function isNumber(x: any): x is number {
  return typeof x === "number";
}
 
function isString(x: any): x is string {
  return typeof x === "string";
}
 
function padLeft(value: string, padding: string | number) {
  if (isNumber(padding)) {
    return Array(padding + 1).join(" ") + value;
  }
  if (isString(padding)) {
    return padding + value;
  }
  throw new Error(`Expected string or number, got '${padding}'.`);
}

However, having to define a function to figure out if a type is a primitive is kind of a pain. Luckily, you don’t need to abstract typeof x === "number" into its own function because TypeScript will recognize it as a type guard on its own. That means we could just write these checks inline.

function padLeft(value: string, padding: string | number) {
  if (typeof padding === "number") {
    return Array(padding + 1).join(" ") + value;
  }
  if (typeof padding === "string") {
    return padding + value;
  }
  throw new Error(`Expected string or number, got '${padding}'.`);
}

These typeof type guards are recognized in two different forms: typeof v === "typename" and typeof v !== "typename", where "typename" can be one of typeof operator’s return values ("undefined", "number", "string", "boolean", "bigint", "symbol", "object", or "function"). While TypeScript won’t stop you from comparing to other strings, the language won’t recognize those expressions as type guards.

# instanceof type guards

If you’ve read about typeof type guards and are familiar with the instanceof operator in JavaScript, you probably have some idea of what this section is about.

instanceof type guards are a way of narrowing types using their constructor function. For instance, let’s borrow our industrial strength string-padder example from earlier:

interface Padder {
  getPaddingString(): string;
}
 
class SpaceRepeatingPadder implements Padder {
  constructor(private numSpaces: number) {}
  getPaddingString() {
    return Array(this.numSpaces + 1).join(" ");
  }
}
 
class StringPadder implements Padder {
  constructor(private value: string) {}
  getPaddingString() {
    return this.value;
  }
}
 
function getRandomPadder() {
  return Math.random() < 0.5
    ? new SpaceRepeatingPadder(4)
    : new StringPadder("  ");
}
 
let padder: Padder = getRandomPadder();
      
let padder: Padder
 
if (padder instanceof SpaceRepeatingPadder) {
  padder;
    
let padder: SpaceRepeatingPadder
}
if (padder instanceof StringPadder) {
  padder;
    
let padder: StringPadder
}

The right side of the instanceof needs to be a constructor function, and TypeScript will narrow down to:

the type of the function’s prototype property if its type is not any the union of types returned by that type’s construct signatures in that order.

# Nullable types

TypeScript has two special types, null and undefined, that have the values null and undefined respectively. I mentioned these briefly in the Types section.

By default, the type checker considers null and undefined assignable to anything. Effectively, null and undefined are valid values of every type. That means it’s not possible to stop them from being assigned to any type, even when you would like to prevent it. The inventor of null, Tony Hoare, calls this his “billion dollar mistake”.

The [strictNullChecks](./AdvancedTypes.md) flag fixes this: when you declare a variable, it doesn’t automatically include null or undefined. You can include them explicitly using a union type:

let exampleString = "foo";
exampleString = null;
Type 'null' is not assignable to type 'string'.
 
let stringOrNull: string | null = "bar";
stringOrNull = null;
 
stringOrNull = undefined;
Type 'undefined' is not assignable to type 'string | null'

Note that TypeScript treats null and undefined differently in order to match JavaScript semantics. string | null is a different type than string | undefined and string | undefined | null.

From TypeScript 3.7 and onwards, you can use optional chaining to simplify working with nullable types.

# Optional parameters and properties

With strictNullChecks, an optional parameter automatically adds | undefined:

function f(x: number, y?: number) {
  return x + (y ?? 0);
}
 
f(1, 2);
f(1);
f(1, undefined);
f(1, null);

// Argument of type 'null' is not assignable to parameter of type 'number | undefined'.

The same is true for optional properties:

class C {
  a: number;
  b?: number;
}
 
let c = new C();
 
c.a = 12;
c.a = undefined;
Type 'undefined' is not assignable to type 'number'.
c.b = 13;
c.b = undefined;
c.b = null;
Type 'null' is not assignable to type 'number | undefined'.

# Type guards and type assertions

Since nullable types are implemented with a union, you need to use a type guard to get rid of the null. Fortunately, this is the same code you’d write in JavaScript:

function f(stringOrNull: string | null): string {
  if (stringOrNull === null) {
    return "default";
  } else {
    return stringOrNull;
  }
}

The null elimination is pretty obvious here, but you can use terser operators too:

function f(stringOrNull: string | null): string {
  return stringOrNull ?? "default";
}

In cases where the compiler can’t eliminate null or undefined, you can use the type assertion operator to manually remove them. The syntax is postfix !: identifier! removes null and undefined from the type of identifier:

interface UserAccount {
  id: number;
  email?: string;
}
 
const user = getUser("admin");
user.id;
Object is possibly 'undefined'.
 
if (user) {
  user.email.length;
Object is possibly 'undefined'.
}
 
// Instead if you are sure that these objects or fields exist, the
// postfix ! lets you short circuit the nullability
user!.email!.length;

# Type Aliases

Type aliases create a new name for a type. Type aliases are sometimes similar to interfaces, but can name primitives, unions, tuples, and any other types that you’d otherwise have to write by hand.

type Second = number;
 
let timeInSecond: number = 10;
let time: Second = 10;

Aliasing doesn’t actually create a new type - it creates a new name to refer to that type. Aliasing a primitive is not terribly useful, though it can be used as a form of documentation.

Just like interfaces, type aliases can also be generic - we can just add type parameters and use them on the right side of the alias declaration:

type Container<T> = { value: T };

We can also have a type alias refer to itself in a property:

type Tree<T> = {
  value: T;
  left?: Tree<T>;
  right?: Tree<T>;
};

Together with intersection types, we can make some pretty mind-bending types:

type LinkedList<Type> = Type & { next: LinkedList<Type> };
 
interface Person {
  name: string;
}
 
let people = getDriversLicenseQueue();
people.name;
people.next.name;
people.next.next.name;
people.next.next.next.name;
        
(property) next: LinkedList<Person>