Starting using TypeScript

Beign mostly an C++ developer, I start a bit late looking into these new things like Node.js and TypeScript and whatnot. But after watching a presentation about TypeScript from Microsoft, I was interested in using it, as Javascript is terrible when you start developing big, complex applications. Its problems becomes more evident now with all the new features of C++11 and newer, and even when compared to new languages like Swift. I know there's a recent version of Javascript, ES2015, but there's no full support for it everywhere.

So, nothing better than putting TypeScript to test making a simple, interesting application. 😁

What does TypeScript?

Typescript is an extension of the old Javascript. The most evident one is beign able to use static types for checking errors related to misuse of types, something not available to Javascript.

But what interest me most about it is compiling TypeScript to old Javascript. Because of that, you can use all the new features that only ES2015 has and have your code working even on browsers that support only ES3. This by itself is more than enough to make me stop using Javascript directly and start coding TypeScript exclusively.

Since it's a compiler, we need to install and configure it to be used with the code editor of your choice.

Installing and Configuring TypeScript

If you use VisualStudio 2013 Update 2 or later as your editor, the TypeScript compiler is already installed by default. Otherwise, we need to install to use it with your code editor of choice, mine being Visual Studio Code.

As it's a Node.js application, we need to install it first.

Then after installing Node.js, we just need to install Typescript:

npm install -g typescript

That's it, we already can compile any TypeScript file to Javascript using:

tsc source.ts

that will create a file named source.js!

Compiler Configuration

In the folder that we'll use to save our code, just create a tsconfig.json file. In this article's example, the file has the following content:

{
    "compileOnSave": true,
    "compilerOptions": {
        "target": "es3",
        "module": "commonjs",
        "sourceMap": true,
        "noEmitOnError": true
    }
}

This configuration file tells the compiler to generate ES3 Javascript code, using a common js interpreter as standard. The sourceMap option generates a .map file that can be used by Firefox to map the generated Javascript code to our TypeScript code while debugging. The noEmitOnError option tells the compiler to not generate any output if there's an error in our code, so we dont overwrite our .js file that is working correctly.

If you are using Visual Studio Code, we just need to create a task that compiles our .ts file everytime we save. Opening the Command Palette, just type task and chosse Configure Task Runner and then TypeScript - Watch Mode so the editor can generate the required configuration. Then, just start the task by issuing a Run Build Task command.

Creating the required files

Firstly we need to create the HTML that we'll use to load our script and to setup the canvas needed to draw. I created the canvas.html with the following code:

<!DOCTYPE hmtl>
<html>
    <head>
        <meta charset="utf8">
        <title>Teste Canvas</title>
    </head>
    <body>
        <canvas id="tela" width="640" height="480"></canvas>
        <script src="canvas.js"></script>
    </body>
</html>

Then, I created a file named canvas.ts that'll have our application code.

Creating the script

To have access to the canvas and start drawing in it, we need the 2D context. Furthermore, we set-up some constantes so the script knows the canvas boundaries.

let tela = document.getElementById("tela") as HTMLCanvasElement;
let ctx = tela.getContext("2d");

const backgroundColor = "#000";
const width = tela.width;
const height = tela.height;

ctx.fillStyle = backgroundColor;
ctx.fillRect(0,0,width,height);

At the first two lines, we get the canvas element and then the 2D context for later use. After that, we define 3 constants, that well be used by the later parts of the script, and finally, we initialize our canvas filling it with a black rectangle, so we can verify that it's working.

A important detail here is that we need to tell TypeScript that the element returned by the document.getElementById call is indeed a canvas, by converting the type HTMLElement to HTMLCanvasElement using the keyword as, as there's no getContext method available from the HTMLElement class.

Creating classes

Our little project has as an objective draw various geometric objects made of a random number of points moving around our canvas, inspired by classic Windows screen-saver, Polygons.

The first class we need is how to represent a point in the 2D space, made of two coordinates x and y. Additionaly, as we have to move the point around the canvas, we need to add an operation that moves the point to a certaing direction represented by another group of coordinates too. So, we have the Vector class.

class Vector {
    x: number;
    y: number;

    constructor( x:number , y:number) {
        this.x = x;
        this.y = y;
    }

    add(o:Vector) {
        this.x += o.x;
        this.y += o.y;
    }
}

Now we can create a class that represents the point with a direction, that'll move around, bouncing at the canvas' sides. Not only it'll have a position and direction information, but it'll have a method to update it's position at each animation frame. We'll call it MovingPoint.

class MovingPoint {
    position: Vector;
    direction: Vector;

    constructor(position:Vector,direction:Vector) {
        this.position = position;
        this.direction = direction;
    }

    move() {
        this.position.add(this.direction);
        if (this.position.x <= 0 || this.position.x >= width)
            this.direction.x = -this.direction.x;
        if (this.position.y <= 0 || this.position.y >= height)
            this.direction.y = -this.direction.y;
    }
}

We need a class that represents a collection of points to make the polygon... But first, as we want to create random polygons, let's create some auxiliary functions:

function randomPos(width:number,height:number): Vector {
    let x = Math.random() * width;
    let y = Math.random() * height;
    return new Vector(x,y);
}

function randomSpeed(min:number, max:number) {
    let val = Math.random() * (2 * max) - max;
    if (val > 0 && val < min) val = min;
    if (val < 0 && val > -min) val = -min;
    return val;
}

function randomDir(minSpeed:number, maxSpeed:number) {
    let x = randomSpeed(minSpeed,maxSpeed);
    let y = randomSpeed(minSpeed,maxSpeed);
    return new Vector(x,y);
}

Now, we just need to create a class that is a group of at least 2 points (so we get a line), that'll be drawn at the canvas using a pre-defined color at each frame. That'll be our Polygon class.

For it, we'll create 2 methods so that each polygon can move it's points and draw lines at the canvas.

class Polygon {
    points: MovingPoint[];
    color: string;
    open: boolean;

    constructor(vectors:number,color:string,open:boolean = false) {
        if (vectors < 2) throw Error("Need at least 2 poins!");
        if (vectors == 2) this.open = true; else this.open = open;
        this.color = color;
        this.points = new Array<MovingPoint>(vectors);
        for (let i = 0; i < this.points.length; ++i) {
            this.points[i] = new MovingPoint(randomPos(width,height),randomDir(1,8));
        }
    }

    update() {
        for (let point of this.points) {
            point.move();
        }
    }

    draw(ctx:CanvasRenderingContext2D) {
        ctx.beginPath();
        ctx.strokeStyle = this.color;
        let {x,y} = this.points[0].position;
        ctx.moveTo(x,y);
        for (let i = 1; i < this.points.length; ++i) {
            let {x,y} = this.points[i].position;
            ctx.lineTo(x,y);
        }
        if (!this.open) ctx.closePath();
        ctx.stroke();
    }
}

While coding this class, we use some of the features only available for ES2015 and TypeScript: in the constructor, we used generics to create an Array of a specific type; in update, we used the new for (let item of collection) that iterates over all the elements of a collection; and in draw we use object destructuring of the Vector type into 2 variables, for more legible code.

Drawing at the Canvas

Now we just need to create some Polygon objects, create a function that well be called on each frame update and it's done!

let objects:Polygon[] = [
    new Polygon(2,"white"),
    new Polygon(3,"orange"),
    new Polygon(4,"rgb(100,150,255)")
];

let render = () => {
    ctx.fillStyle = "rgba(0,0,0,.075)";
    ctx.fillRect(0,0,width,height);
    for (let obj of objects) {
        obj.update();
        obj.draw(ctx);
    }
}

let renderingLoop = window.setInterval(render,33);

Note that render uses the new lambda sintax to create the function. This new syntax creates the correct this bindings without the need to create a function scope like we have to do on old Javascript, making the code less prone to errors.

Now just save the file, confirm that the new canvas.js was created by the TypeScript compiler in the same folder, and open our html in a browser to see the result!

References

Node.js: https://nodejs.org/

TypeScript: https://www.typescriptlang.org/

Visual Studio Code: https://code.visualstudio.com/

Canvas: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API