Typeguards in Typescript

Typeguards enable us to process function arguments based on their types.

Consider this example -

function join(a: string): string {
  return a;
}

console.log(join("hello")); // hello

What if we want to use join for strings or numbers? We can do a union type like so -

function join(a: string | number): string | number {
  return a;
}

console.log(join("hello")); // hello
console.log(join(1)); // 1

But, a real world problem would lie in processing the input arguments - not just returning them. Also, the methods and properties of the argument will change within a function based on their types. Typescript solves this problem using Typeguard.

Typeguard is a way if isolating types within an argument in specific blocks. Typescript provides different ways of solving this problem in different situations - typeof, instanceof, literals and in. You can also go ahead and create your own typeguards if none of those satisfy your use case.

typeof

We can build on the previous example -

function join(a: string | number, b: string | number): string | number {
  if (typeof a == "string" && typeof b == "string") {
    return "".concat(a, " ", b);
  } else if (typeof a == "number" && typeof b == "number") {
    return "".concat("".concat(String(a), String(b)));
  }
}

console.log(join("hello", "world")); // hello world
console.log(join(1, 2)); // 12

By simply the process of checking the type with typeof a == "string", Typescript will infer that anything in the block will use a as a string. So, you can easily use a.toUpperCase() within the block but a.toFixed(0) will produce a compilation error.

In the above code block, you could drop the else if and include just an else to get the same result.

function join(a: string | number, b: string | number): string | number {
  if (typeof a == "string" && typeof b == "string") {
    return "".concat(a, " ", b);
  } else {
    return "".concat("".concat(String(a), String(b)));
  }
}

console.log(join("hello", "world")); // hello world
console.log(join(1, 2)); // 12

Typescript will understand that the only other type option for a and b is a number. So it will apply number properties and methods to the variables within the else block.

instanceof

Similar to the use-case for variables described above, instanceof applies itself to user-defined classes and objects.

class Earth {
  color = "blue";
}
class Mars {
  color = "red";
}

function colorPlanet(planet: Earth | Mars) {
  if (planet instanceof Earth) {
    console.log(planet.color);
  } else {
    console.log(planet.color);
  }
}

colorPlanet(new Earth()); // blue
colorPlanet(new Mars()); // red

in

in is similar to instanceof but rather than using an instance of the class, we use a prop of the object to distinguish between two types.

class Earth {
  life = true;
  color = "blue";
}
class Mars {
  color = "red";
}

function processPlanet(planet: Earth | Mars) {
  if ("life" in planet) {
    console.log(planet.color);
  } else {
    console.log(planet.color);
  }
}

processPlanet(new Earth()); // blue
processPlanet(new Mars()); // red

literals

Literals just refer to literal values within an object and to use the possible options as typeguards.

type custHappy = "yes" | "no";

function loadSurvey(sat: custHappy) {
  if (sat == "yes") {
    console.log("happy face");
    // loadPublicSurvey();
    //upSellHell();
  } else {
    console.log("frown face");
    // loadPrivateSurvey();
  }
}
comments powered by Disqus