26 Nov 2018

TypeScript generics in a dark castle

Welcome to the castle!

darkcastle.png

So - JavaScript! Fans of the language praise the flexibility. Opponents argue that the headaches of loose types, which is part of what makes that flexibility possible, are too severe to be worth it.

Then - TypeScript! Typescript adds strong types to JavaScript, without sacrificing almost any of the flexibility. At Edument we’ve been collectively wooed by how easily TypeScript elevates our JavaScript coding experience to the next level.

A very powerful feature of strong type checking is generics. It is also a feature that is commonly hard to grasp, so in this post we’ll now explore an illustrative example!

In our Angular course we often coax the participants into making an app version of a classic “choose-your-own-adventure” gamebook:

gamebook.png

They get to define their adventure as JSON data, looking something like this:

const theDarkCastle = {
 beginning: {
   title: "The journey begins",
   description: "You wake up in a strange room. …",
   options: [
     { text: "Open the door", targetPage: 'hallway' },
     { text: "Climb out the window", targetPage: 'precipice' },
   ]
 },
 hallway: {
   title: "A dark hallway",
   description: "The hallway ends with two doors.",
   options: [
     { text: "The door on the left", targetPage: 'guardroom' },
     { text: "The door on the right", targetPage: 'staircase' },
     { text: "Go back", targetPage: 'beginning' },
   ]
 },
 // ...lots more pages...
}

Being able to whip up large objects like that, without going via a predefined class, is a good example of JavaScript’s flexibility.

But! Without tooling it would be very easy to make mistakes while typing out that data. Therefore we make the participants define a TypeScript interface for their adventure, looking something like this:

type Adventure = {
 [pageId: string]: {
   title: string,
   description: string,
   options: {
     text: string
     targetPage: string
   }[]
 }
}

Now, by type-casting the data as an Adventure, we’ll be warned if we make a mistake as we type:

typo-deskription.png

Note the angry red squiggly line because we misspelt “description”.

However, there’s one place in our adventure where we won’t get help - the targetPage parts in the options that points to other pages! TypeScript thinks they’re just strings now, but in reality they’re strings that also has to be existing keys in our adventure object!

Now, we could make a special type for our page id:s…

type DarkCastlePageId = 'beginning' | 'hallway' | 'hallway2' | 'precipice' // ...and lots more

...and use that in our adventure type instead:

type Adventure = {
 [pageId in DarkCastlePageId]: {
   title: string,
   description: string,
   options: {
     text: string
     targetPage: DarkCastlePageId
   }[]
 }
}

Now TypeScript can save us if we type “precipise” instead of the correct key “precipice”:

typo-presipise.png

Victory! But, we have a problem. The app we’re building is meant to be like a game engine - it should be able to run ANY adventure, as long as the data has the correct shape. But now we’ve hard-coded the page id:s for The Dark Castle into the definition of what an adventure should look like!

This is where generics come in. They let us pass arguments to types!

type Adventure<PageIdType extends string = string> = {
 [pageId in PageIdType]: {
   title: string,
   description: string,
   options: {
     text: string
     targetPage: PageIdType
   }[]
 }
}

Essentially we just said that…

  • The Adventure type takes a parameter type
  • That parameter needs to be an extension of the string type
  • If nothing is passed in, default to string

We now type the Dark Castle adventure by passing in our previously created DarkCastlePageId type to the Adventure type...

const theDarkCastle: Adventure<DarkCastlePageId> = {
 beginning: {
   title: "The journey begins",

...and you can of course type your own adventure by passing in a different argument, yet still run it in the same engine!

const myLittlePony: Adventure<MyLittlePonyPageId> = {
 stable: {
   title: "In the stable",

In the Angular course we’ll rarely go beyond what you’ve just seen (although once or twice we’ve shown the dark magic with which you can autogenerate the pageId types), since TypeScript is just a tool for the main dish which is Angular.

But in the TypeScript course we have no such limits! There we take our participants deep down the rabbit hole, exploring generics and other typing tools in depth.

Related courses

  • The New Angular

    Take this course to not just learn how the new Angular framework works, but also gain a deep understanding of the thinking that comes with it!

    Duration: 2 days
    Price: 21 500 SEK
  • TypeScript

    TypeScript starts from JavaScript and turns it into a safe language. You get gradual type checking, code completion, as well as static checking of the way your code hangs together. JavaScript never had the hard, protective shell that we're used to in contemporary languages. TypeScript acts like a programming exoskeleton, giving you safety, expressive power, and precision on top of the original language. 

    Duration: 2 days
    Price: 21 500 SEK