Building Your Own Types
The template code shows some TypeScript features that can make you more productive. Now it’s time to build new functionality and demonstrate how TypeScript can make you more productive in larger applications.
Space in this article doesn’t allow for building a large-scale application. Instead, I’ll walk through a few small features of an application, building them as though they were part of a large-scale application. You should consider how these features would enable large-scale development in your organization.
The features I’ll add will display dice rolls, including the total of the roll (see Figure 1). Every second, a new dice roll is generated.
Figure 1 The sample application generates a new random dice roll every second.
I’ll begin building this application by creating a Die class to represent a single roll. A die object needs two properties: an HTML image element to display the appropriate image, and a number to represent the die roll (check out branch 06-defineDieClass):
class Die { dieElement: HTMLImageElement; public dieRoll: number; } This class definition creates an empty JavaScript type: var Die = (function (){ function Die(){ } return Die; })();
I haven’t written any code that uses the properties of the Die object yet, so I don’t need to add those properties to the generated JavaScript. Next, let’s add the constructor. I’d like to add two constructors to the Die class: one takes a number for the die roll, and the other generates the roll as a random number. TypeScript supports method overloading in a manner that is consistent with JavaScript. JavaScript doesn’t support overloaded methods, but supports writing methods that inspect the arguments directly.
TypeScript supports method overloading, as long as all the method overloads can be implemented with a single definition. You declare multiple prototypes for the method, and write a single implementation that works with each of the supported prototypes. Here, I declare two prototypes: one with no parameters, and one with an optional number. The implementation examines the parameter, and either sets the die value to the value of the parameter, or generates a number between 1 and 6 (check out branch 07-createDieConstructor):
constructor(); constructor(value?: number) { this.dieElement = document.createElement("img"); this.dieRoll = value || Math.floor(Math.random() * 6 + 1); this.dieElement.setAttribute("src", this.getImgSrc()); }
The associated JavaScript code now initializes the properties when a Die object is created. More work is involved in the Die constructor, and it’s a chance to show a couple of TypeScript’s static analysis features. I’ll use a private method to initialize the HTMLImageElement once the number has been computed (check out branch 08-call-getImgSrc):
constructor(); constructor(value?: number) { this.dieElement = document.createElement("img"); this.dieRoll = value || Math.floor(Math.random() * 6 + 1); this.dieElement.setAttribute("src", this.getImgSrc()); }
After I add this call, die.ts doesn’t compile; getImgSrc() has no definition. TypeScript knows that this method has not been defined yet. Once I add the implementation (check out branch 09-implement-getImgSrc), this class compiles.
Before we move on, I want to show one more important TypeScript feature. Here’s the implementation of getImgSrc:
private getImgSrc() { var imgSrc: string; switch (this.dieRoll) { case 1: return "/images/dice1.png"; case 2: return "/images/dice2.png"; case 3: return "/images/dice3.png"; case 4: return "/images/dice4.png"; case 5: return "/images/dice5.png"; case 6: return "/images/dice6.png"; } }
Notice that this implementation doesn’t specify a return type—often an option in TypeScript, because the compiler can infer the return type (string, in this method). If desired, I could be explicit about the return type:
private getImgSrc() : string {
Equally importantly, if I add a mistaken default clause to the switch statement, the TypeScript compiler will catch it, even if the return type is not explicitly stated:
private getImgSrc() { var imgSrc: string; switch (this.dieRoll) { case 1: return "/images/dice1.png"; case 2: return "/images/dice2.png"; case 3: return "/images/dice3.png"; case 4: return "/images/dice4.png"; case 5: return "/images/dice5.png"; case 6: return "/images/dice6.png"; default: return 0; } }
The compiler complains that it can’t find a best common type for all return statements in the method. I’ll fix the default case by returning the empty string. The Die class also contains a small method to add the dieElement to a container element. You can see the completed class by checking out branch 10-completeDieImplementation.