Type Assertions - the as keyword

When using TypeScript it can be tempting to use Type Assertions as a way of saying "trust me TypeScript, I know the type, hush now.".

However, this can be risky. You have now broken the safety contract that TypeScript normally provides.

For example:

const element = document.getElementById("this-is-definitely-an-anchor");
if (element) {
  // Type Assertion - trust me, it's an anchor
  console.log((element as HTMLAnchorElement).href.includes("https://"));
}
TypeScript Playground

In the above example the Type Assertion as HTMLAnchorElement is telling TypeScript "trust me, I know this is definitely an anchor element. So don't bother warning me that href might not be defined".

What's the problem?

It's clearly an anchor, it's not hurting anyone, why are we getting all worked up?

The true beauty of TypeScript, similar to unit tests, is it protects your future self, not just your current self. If you later refactor some code, you want TypeScript to complain at compile time, instead of failing at run time.

Your colleague, you know the one, always changing stuff, let's call him Jeff. Jeff updates the html from an anchor to a button. Your artisan code above will now fail at run time (in the browser)!

Uncaught TypeError: Cannot read properties of undefined (reading 'includes')

It fails because we assured TypeScript, through our assertion, that the element was an anchor. Maybe it was our confidence, maybe our bravado, but TypeScript believed us without question. Don't get me wrong, Jeff is also to blame for changing the HTML.

What is a better way to handle unknowns?

Instead of saying "trust me", you should prove to TypeScript that the unknown is a particular type by "narrowing it".

instanceof is one way to do this:

const element = document.getElementById("this-is-definitely-an-anchor");
if (element && element instanceof HTMLAnchorElement) {
  console.log(element.href.includes("https://"));
}
TypeScript Playground

Sometimes you may not have a constructor to reference. You can also narrow types with the in operator:

const payload = JSON.parse(request.body);
if ('message' in payload) {
  console.log(payload.message.toUpperCase());
}
TypeScript Playground

To be super clear, both instanceof and in are JavaScript runtime operators. This is very intentional as you are proving to TypeScript that you will be handling it at runtime.

Type predicates are another useful technique. I will do a separate write up on those.