Use the power of TypeScript

My recent post was a brief introduction into TypeScript to show you what it is and why it's worth to use this language as JavaScript replacement. But I feel ashamed a bit because I showed you almost no examples so how can you feel conviced to that? To fix the issue I've decided to write an another article showing you few implementations in this awesome language. So grab a popcorn, some beer or whatever you want and read on!


First, you need to install TypeScript on your machine. TypeScript compiler exists as an NPM package, so assuming you have node.js and NPM installed, simply type:

npm install -g typescript  

And we are done! It's everything you need to compile simple TypeScript file into JavaScript file that could be properly interpreted by a browser or node.js.

To make sure our compiler works as expected, let's create a simple file:

var name: string = "World";  
console.log("Hello " + name + "!");  

Then run the TypeScript compiler from your command line:

tsc yourFile.ts  

You should get a file with almost the same content. It should be called the same same as your *.ts file but with *.js extension in the same directory:

var name = "World";  
console.log("Hello " + name + "!");  

Great! Now we have TypeScript installed so basically we can begin the party.

As you can see the file we created is very similar to compiled one. As I mentioned in my previous post, if you'd put some pure JavaScript code into a TypeScript file, you'll get get the same file but with different extension. That's it.

Let's make our JavaScipts better

Now I'd like to show you how helpful TypeScript can be during the development. Imagine you have a script adding log messages to some container:

var LogContainter = function () {  
    this.messageQueue = [];
};

LogContainer.prototype.queueMessage = function (message) {  
    this.messageQueue.push(message);
};

LogContainer.prototype.displayQueuedMessages = function () {  
    for (var i = 0; i < this.messageQueue; i++) {
        var message = this.messageQueue[i];
        var date = message.date;
        var dateString = date.getDate() + " "
            + date.getMonth() + 1 + " "
            + date.getFullYear();

        console.log("[" + dateString + "]" + message.logMessage);
    }
};

It's a script that uses JavaScript prototyping to create simple class for queuing log message just to display them on demand. It consist of queueMessage method to add a message to the queue and displayQueuedMessages to display all of them in custom format. Seems ok, doesn't it?

If you look closer there are numerous elements that can break our script. For example, queueMessage method expects that message passed as a parameter will be an object containing date and logMessage parameter. There is no validation like in Java that element that we want to add must be of certain type. Next, we expect that date property in log object will be a Date object and a logMessage will be a string that can be concatenated to the date. Those parameters aren't verified anywhere. Of course we can fix all of these problems by putting validators here and there but... can you imagine this amount of code we need to add here? It's a bit problematic. It can make some problems for programmer just to make sure that input data is valid.

However, we'll try to make our code a bit cleaner. At first we must be sure that our log message consist only of a date and a message. So can't we just create a separate object for that?

Of course we can! To simplify the whole process, let's add some object's that will contain our data:

var LogMessage = function (message, date) {  
    this.message = message;
    this.date = date;
};

Great. Now we can construct a LogMessage object and pass it into queueMessage method. Example usage can look like:

var logContainer = new LogContainer();  
var msg = new LogMessage("Connected to the backend", Date.now());

logContainer.queueMessage(msg);  
logContainer.displayQueuedMessages();

// Outputs:
// [13-11-2015] Connected to the backend

Our code works but LogContainer class code is still a bit unreadable. The IDE you use probably doesn't verify that the message you pass consist of those two parameters. Moreover it's hard to determine that we have a class here, it's just a bunch of functions. Let's fix it.

TypeScript to the rescue!

Remember when I've said that TypeScript is fully backward compatible with JavaScript? It means that any JS code in .ts file will be compiled into exactly the same file with .js extension. Let's do it now. Create a filed called logs.ts and copy all code that we have already written.

var LogContainer = function () {  
    this.messageQueue = [];
};

LogContainer.prototype.queueMessage = function (message) {  
    this.messageQueue.push(message);
};

LogContainer.prototype.displayQueuedMessages = function () {  
    for (var i = 0; i < this.messageQueue; i++) {
        var message = this.messageQueue[i];
        var date = message.date;
        var dateString = date.getDate() + " "
            + date.getMonth() + 1 + " "
            + date.getFullYear();

        console.log("[" + dateString + "]" + message.logMessage);
    }
};

var LogMessage = function (message, date) {  
    this.message = message;
    this.date = date;
};

var logContainer = new LogContainer();  
var msg = new LogMessage("Connected to the backend", Date.now());

logContainer.queueMessage(msg);  
logContainer.displayQueuedMessages();  

Now we're going to rewrite our code to TypeScript step by step.

TypeScript allows you to declare classes in your code. The syntax is a bit different from that you know from Java or C# but still very reasonable. If you'd like to create a constructor, in Java you would create a method called the same as owner class. In TypeScript you need to create a constructor method. Let's create our first classes for LogContainer and LogMessage:

class LogMessage {  
    private message: string;
    private date: any;

    constructor(message: string, date: any) {
        this.message = message;
        this.date = date;
    }
}

class LogContainer {  
    private messageQueue = [];
}

Let's stop for a moment, we have a lot of code things to explain here.
We have created two classes here, no big surprise. The LogMessage class has two private fields: message of string type and date of ... any type? That's right, TypeScript has a special data type - any - for objects that can't be classified or we simply don't care what is it. But we know we want a Date here, don't we?
Well, it isn't that simple. For now, TypeScript provides support for basic built-in types:

  • boolean
  • string
  • number
  • array
  • enum
  • void

If we want to type a property to certain class, it need to be declared in .ts files or we have to create a definition file. For now remember that we can't simply type our property to Date so we'll either type to any or create a proxy class for Date. I will cover definition files and proxy classes in another articles. Stay tuned!

Let's go back to the code. Now we know that we have the LogMessage class with two private properties. Now let's take a look on a constructor. Nothing special, really. We pass two parameters and we assign them to the object's fields. The thing I wanted to remark is that fields are in the same type as fields we assign to. It's important to keep consistency, or we'll get a compiler errors.

class Foo {  
    private myField: number;
    constructor (myParameter: boolean) {
        this.myField = myParameter; // Compiler error!
    }
}

Now I would like to introduce you a great TypeScript feature that, in my opinion, many OOP programmers will appreciate. Take a look at the constructor one more time. You can see that each parameter here has the same name as class field. What if I tell you that you can declare and assign class fields even more easily?

class LogMessage {  
    constructor(private message: string, private date: any) {}
}

See what I did here?
Note that each parameter has a private keyword before. It's just implicit fields initialization, a very helpful shorthand, useful when we want to pass parameters to the constructor just to assign them in private fields. If we would like to initialize public properties here, no problem, use public instead of private here.

Ok, so we have our LogMessage class ready. Let's take a look at the LogContainer.

class LogContainer {  
    private messageQueue = [];
}

We haven't declared a constructor here just because it's not necessary. We don't pass any parameters from the outside to create an object. Constructors are not obligatory in TypeScript. Moreover, I haven't declared a type for messageQueue field. Well, we don't have to. Here we have an implicit type of array because we declared this field with assignment. But we want to use whole of TypeScript goodness, don't we?

Hello, typed arrays

Arrays in JavaScript, like in any other dynamically typed languages, are quite specific. We don't have to provide fixed length or type. Dynamic lengths, cool feature, we don't have to care about memory allocation here. But in this case we would like to indicate that our array will consist only of LogMessage objects. Here's another awesome feature in TypeScript. Take a look:

class LogContainer {  
    private messageQueue: Array<LogMessage> = [];
}

We have declared that messageQueue is an array of LogMessages. Any other elements can't be added to it.
Array type in TypeScript is a bit different from other ones. We should always provide a type for its elements. If we want to keep any changes, we'll use... array of any elements, like that:

var myArray: Array<any> = [1, 'my string', true];  

But it's always good to show the type of elements so try to avoid this.

Let's get back to the code for a moment. Have you notices those < and >? This symbols are reserved for generic types. I'll write another post about generics but remember that typed between those brackets will affect the object inside. In this example, LogMessage type will cause that every element in array will be of this type.

Array is one the most common type we use in our scripts. Writing such long type for a variable can shadow its readability and can be easy to mistype. If you also find it difficult to read, you can use short array syntax as well.

class LogContainer {  
    private messageQueue: LogMessage[] = [];
}

Much better, isn't it?

OK, but where are the methods?

So far, our code looks like this:

class LogMessage {  
    constructor(private message: string, private date: any) {}
}

class LogContainer {  
    private messageQueue: LogMessage[] = [];
}

One-line classes. Isn't it beautiful? Let's add some methods that we've omitted. First, the queueMessage method:

class LogContainer {  
    private messageQueue: LogMessage[] = [];

    queueMessage(message: LogMessage): void {
        this.messageQueue.push(message);
    }
}

As you can see, creating a class method isn't a rocket-science. It's important to note that we don't have a function keyword here. TypeScript compiler knows that because we have parameter lists and curly brackets - elements specific for functions.
Next, we expect that method will be invoked only with LogMessage object. It's necessary to keep consistency here. We know that our method will push the object to the messageQueue and we know that queue is also LogMessage-array. We have also declare a return type of a method - void - which means that it returns nothing. The return type is also not obligatory but it's a good practice to write this, just to make sure that we know what a method returns here. If it would be declared with, for example, number type and return string, the compilation will fail.

Ok, let's add another method.

class LogContainer {  
    private messageQueue: LogMessage[] = [];

    queueMessage(message: LogMessage): void {
        this.messageQueue.push(message);
    }

    displayQueuedMessages(): boolean {
        for (let i: number = 0, len: number = this.messageQueue.length; i < len; i++) {
            var logEntry: LogMessage = this.messageQueue[i];
            var date = logEntry.date;
            var dateString = date.getDate() + " "
                + date.getMonth() + 1 + " "
                + date.getFullYear();

            console.log(`[${dateString}] ${logEntry.message}`);
        }

        return true;
    }
}

This method contains another bunch of features that I haven't said about yet. The displayQueuedMessages is boolean type - I changed this behavior just to show you how it can look to return any other type that void.

Take a look on 9th line. I use the let keyword which is new feature also in EcmaScript6. This keyword can be used instad of var to change variable scope. If I'd use var here, the i variable would be accessible even outside for loop. With let, when loop ends its work, it's not available anymore.

Inside the for loop we have almost the same code. Well, almost. You can see that logEntry, previously a message in our JS code, is typed to LogMessage but it's not necessary. While iterating over the messageQueue array TypeScript knows that there are no other elements but LogMessage objects.

But there's another strange thing. Note that in line 11 I'm using the private field of an object. You're right, it's not correct. And there's no magic here. When editing this code, I've added a getter for date field in LogMessage. Why?
The LogMessage object should be immutable. Once created, it shouldn't be modified from outside. But I still want to access it's properties. I could do it by declaring a method: getDate() in LogMessage but then I would have to change all occurences of .date into .getDate(). It can be quite problematic. I don't want to expose the date variable but I want it to be in read only mode. Syntax of such getter is trivial and very similar to JavaScript getters:

class LogMessage {  
    constructor(private logMessage: string, private logDate: any) {}

    get date(): any {
        return this.logDate;
    }

    get message(): string {
        return this.logMessage;
    }
}

In the same way I've created a getted for message field. If you've noticed, I used it in displayQueuedMessages method in the last line of for loop. Moreover, it's important to not to use the same name for getter as for field name. That's why I've also changed constructor parameters names. If I'd leave those parameters as before, compiler would throw a failure:

logs.ts(2,25): error TS2300: Duplicate identifier 'message'.  
logs.ts(2,50): error TS2300: Duplicate identifier 'date'.  

Ok, so if we're talking about the message, take a look on what I've done with console.log function in displayQueuedMessages method. Do you see those backticks (`)? It's another feature, also available in ES6, called string interpolation, also known as template strings. It allows you to use expressions inside your string with no need to concatenate a number of mini-strings. Very useful!


Ok but our method still have a lot of code that it shouldn't be responsible for: dateString initialization. I think it could be better if we'd move it to date getter of LogMessage, just to keep consistency, when any other object will use LogMessage's date. I'm going to use already existent date getter and also known interpolation.

var logContainer = new LogContainer();  
var msg = new LogMessage("Connected to the backend", Date.now());

logContainer.queueMessage(msg);  
logContainer.displayQueuedMessages();

// Outputs:
// [13-11-2015] Connected to the backend

Note that I've also changed a return type for date() - since it uses interpolation to output date in proper format, we need to change to string.

Great! Now we can use our getter in displayQueuedMessages method:

LogContainer

Much better, don't you think? Now, the only responsibility of this method is to use some LogMessage properties to display then in custom format. For clarity, here's the whole code we've created in this tutorial:

.ts

Isn't it sexy? Look, how much things we have simplified and how much more readable is this code!

Work it, make it, compile it!

All we have to do now is compile our code!. Let's go:
.js

What...? Oh, come on! What's going on?
TypeScript compiler has told us that we need to target our compilation to ECMAScript 5 if we want to use accessors. Line numbers point to the getters we use for date and message. Where's the problem?
By default, TypeScript will compile our sources to... ehm... ECMAScript 3. And you can see it in help message for tsc:
logs.ts

Ok, at least we know what to do now. Let's try again, with -t switch:

var LogContainer = function () {  
    this.messageQueue = [];
};

LogContainer.prototype.queueMessage = function (message) {  
    this.messageQueue.push(message);
};

LogContainer.prototype.displayQueuedMessages = function () {  
    for (var i = 0; i < this.messageQueue; i++) {
        var message = this.messageQueue[i];
        var date = message.date;
        var dateString = date.getDate() + " "
            + date.getMonth() + 1 + " "
            + date.getFullYear();

        console.log("[" + dateString + "]" + message.logMessage);
    }
};

var LogMessage = function (message, date) {  
    this.message = message;
    this.date = date;
};

var logContainer = new LogContainer();  
var msg = new LogMessage("Connected to the backend", Date.now());

logContainer.queueMessage(msg);  
logContainer.displayQueuedMessages();  

Hurray! Now we have a compiled JavaScript file! Since it's not any specific code, you can use it wherever you want - in your browser, node.js or phone. You can also take a look on your compiled file (logs.js) and see what happened with your black TypeScript magic.


I hope you enjoyed this tutorial. I haven't introduced everything that you could do in TypeScript. There's so much to write about and I think I'll do it very soon.

I'm afraid that this could a bit too long for you but when I've started to write I couldn't stop - there was so many things to describe with details that splitting this article into two parts would make it even more difficult to read. Tell me what do you think about it, feel free to comment and review the code. Any suggestions are welcome.

Stay tuned! New posts are coming soon!