An Introduction to TypeScript

TypeScript is an open-source JavaScript compiler, developed by Microsoft. It's basically a superset of JavaScript that allows optional typing and object-orientated programming paradigms. It was developed by the lead architect of C# and has syntax heavily influenced by C# and Java, which is great for developers jumping into JavaScript from those languages.

By adding features such as classes, modules, interfaces, etc., it creates a strongly typed language which is then compiled down to plain, ECMAScript 3 compatible, JavaScript code. It also offers support for the latest features of JavaScript, such as ECMAScript 6, and even some from future proposals. TypeScript is currently, at time of writing, at version 1.8, with the next major release of 2.0 due soon.

In this article I'm going to look at some of the reasons why you would use TypeScript in your projects, and go through some of the core features of the language, such as the type system, classes, modules, and interfaces.

Why Should I Use TypeScript?

TypeScript has gained support from some of the most popular, modern JavaScript frameworks. The release of Angular 2.0 came with full support of TypeScript, replacing their own AtScript language. There are also developing attempts at supporting TypeScript in other frameworks, such as Ember.js and React.

TypeScript is also great for maintaining large-scale JavaScript apps, as having a strong type system will help to avoid obvious errors.

Integrations with tooling systems, like Gulp and Grunt, can easily be set up to compile entire TypeScript projects into JavaScript. This can further speed up development, and makes the compilation process much simpler for developers.

Not everyone agrees that TypeScript is a great solution, though, and it's interesting to see some other perspectives on this.

Installing TypeScript

Standard TypeScript (.ts) files can't be read by your browser, so they need to be compiled into JavaScript to run. In this article we're going to be using the manual TypeScript compiler on the terminal, to compile out files into standardized JavaScript.

You can install the latest version of the TypeScript compiler globally using npm. Once the TypeScript compiler has installed, we can check it was successful by using the -v version flag. This will require Node to be installed on your machine, which you can install here if necessary.

$ npm install -g typescript
$ tsc -v
message TS6029: Version 1.8.10  

Compiling TypeScript Programs

Another great thing about TypeScript is that, because it's a subset of JavaScript, we can write regular JavaScript code and extend it using whatever TypeScript functionality we would like.

So, for example, let's create a TypeScript file called hello-world.tsc which contains the following code:

// hello-world.ts
console.log('Hello World!');  

This is obviously valid JavaScript, as all we are doing is logging 'Hello World' to the console, which means we can compile it with no problems.

$ tsc hello-world.ts # compile into JS
$ node hello-world.js # run the compiled code

The Type System

TypeScript is basically JavaScript, but with optional types. This means that variables can be assigned a data type on declaration, using a colon. When referring to a type, it is possible to define them as either lowercase or capitalized.

var age : number = 21;  

When this file is compiled using the command line tsc file.ts, the output JavaScript will remove the data type from the declaration.

var age = 21;  

The compiler removes the types because they are not important when the JavaScript is running, but are important during compile time. Allocating data types to a variable is a great way of detecting errors during compilation. For example, if we redefined the age variable with a string, the compiler will throw a useful error at us to let us know what has happened.

$ tsc file.ts 
file.ts(3,1): error TS2322: Type 'string' is not assignable to type 'number'.  

This is because the variable will only accept a value with the data type that was defined. If we are unsure of what the data type should be at this point, it is possible to use the any type, which will be explained later on.

Data Types

The data types supported in TypeScript are basically the same as JavaScript, with the addition of an enum type, much like in C#.

Boolean: simple true/false value.

let isFinished : boolean = true;  

Number: standard floating point value, accepts decimal, hexadecimal, binary and octal literals.

let counter : number = 2;  

String: textual data, defined with single or double quotes.

let name : string = 'Daniel';  

Array: array of values, defined as a vector by specifying the data type followed by []. Value type can be string, number, any, etc.

let numberList : number[] = [1, 2, 3, 4, 5];  
let anything : any[] = ['Daniel', 5, true];  

Enum: An enum is basically a way of naming sets of numeric values, to make them more user friendly and easier to reference.

enum Food { Pizza, Sushi, Noodles };  
var f : Food = Food.Sushi;  

Any: If we are unsure of what the data type may be for a variable we are defining, we are able to use the any type. These values may be coming from a dynamic source, such as a third-party library, and will allow the declaration to pass through the compilation. For most situations, using the any type is usually not recommended, as it defeats the purpose of using types in the first place.

Void: Void should be used in the absence of having any type at all. It is used as the return type for functions that do not return a value.

Interfaces

An interface is another way that TypeScript checks the code for errors during compilation. It can be described basically as a 'blueprint for an object'. Interfaces are really useful for allowing us to tell the compiler exactly what to expect from the object.

When defining an interface, properties are passed in, with their type declarations, to ensure the object will only accept what we want it to. Optional variables can be declared by using a question mark after the variable name.

// interface.ts
interface Humans {  
  name : string;
  age? : number;
}

function printHuman(human : Humans) {  
  console.log('Humans name is: ' + human.name);
  console.log('Humans age is: ' + human.age);
}
printHuman({name : 'Daniel', age : 21});  

So in this example, we have created a simple interface object called Humans that accepts two variables: a string called name and an optional number called age. We have then defined a function that prints out the Humans name and age to the console and then called the function with data passed in to the properties.

When this file is compiled, the returned JavaScript will remove the interface declaration, and simply pass the object to the printHuman() function.

// interface.js
function printHuman(human) {  
  console.log('Humans name is: ' + human.name);
  console.log('Humans age is: ' + human.age);
}
printHuman({ name: 'Daniel', age: 21 });  

If we were to run this file on the command line, we would be returned the Humans data as expected.

$ node interface.js
Humans name is: Daniel  
Humans age is: 21  

Classes

Classes are similar in functionality to interfaces, as in they make up a blueprint for an object, but are generally more powerful. Classes have constructors, which is a reusable bit of code to allow new instances of the classes objects to be created, using the new keyword. This functionality allows a more object-orientated approach and has syntax very similar to Java and C#. Classes are also supported in ES6, using the Babel compiler.

Class properties cannot be declared optional, as they can with interfaces, but classes support the ability to implement interfaces inside themselves with optional properties.

// classes.ts
class Coffee {  
  customerName: string;
  price : string;
  constructor(customerName : string, price : string) {
    this.customerName = customerName;
    this.price = price;
  }
  buyCoffee() {
    console.log('Hi, ' + this.customerName + '! Here is your coffee. That\'ll be ' + this.price + ', please!');
  }
}

let daniel = new Coffee('Daniel', '£2.50');  
daniel.buyCoffee();  

This example of a class is creating a Coffee object, and declared two string variables for the customerName and price of the coffee object. The class also has a constructor to create an instance of the Coffee object by setting values to the objects properties.

We also have a method, buyCoffee(), defined outside of the constructor, which can be called during run time, and is simply returning a conjoined string to the console to telling the customer the price of their coffee. The class object and declared variables will compile into an empty function in JavaScript, as this is unaffected during run time.

// classes.js
var Coffee = (function () {  
    function Coffee(customerName, price) {
        this.customerName = customerName;
        this.price = price;
    }
    Coffee.prototype.buyCoffee = function () {
        console.log('Hi, ' + this.customerName + '! Here is your coffee. That\'ll be ' + this.price + ', please!');
    };
    return Coffee;
}());
var daniel = new Coffee('Daniel', '£2.50');  
daniel.buyCoffee();  

Now when we compile the file, tsc classes.ts, the compiled JavaScript removes the constructor and defines the Coffee object as a function.

$ node classes.js
Hi, Daniel! Here is your coffee. That'll be £2.50, please!  

If we run the compiled JavaScript, we should be returned a string just as expected.

Inheritance

Following the object-orientated patterns, classes can be extended into subclasses using inheritance. This means we can create new classes based on existing ones, such as classes Zebra and Gorilla inherited from class Animal. The extended classes will automatically inherit all variables and constructors from the original class, and can be overridden and extended to create a more specific subclass.

For this example, I'm going to refactor the original Coffee class, and extend it into a Latte class, where a customer can make an order with a different price.

// inheritance.ts
class Coffee {  
  customerName: string;
  constructor(customerName : string) {
    this.customerName = customerName;
  }
  buyCoffee(price : string = '£2.50') {
    console.log('Hi, ' + this.customerName + '! Here is your coffee. That\'ll be ' + price + ', please!');
  }
}

class Latte extends Coffee {  
  constructor(customerName : string) {
    super(customerName);
  }
  buyCoffee(price = '£2.75') {
    super.buyCoffee(price);
  }
}

let order1 = new Coffee('Mike').buyCoffee();  
let order2 = new Latte('Daniel').buyCoffee();  

Here we have extended the original Coffee class and are overriding the buyCoffee() function. Overriden classes don't automatically call the constructor functions from the extended class, which means we need to call it using super(). We are also using super on the extended buyCoffee() function as we have altered the price value, but want the function to execute in just the same way as before.

Now, if we compile and run this file, we'll see the output for two different orders, with different prices for each type of coffee.

$ node inheritance.js
Hi, Mike! Here is your coffee. That'll be £2.50, please!  
Hi, Daniel! Here is your coffee. That'll be £2.75, please!  

There is much more we can do with classes and inheritance in TypeScript, so check out the handbook documentation for more.

Modules

With ES6, and many modern JavaScript frameworks, there is a concept of modules in JavaScript, which is closely shared in TypeScript. They are basically a way to share code between files, to create more structured code throughout your projects. This helps to keep your general file sizes small, and dependencies for your project much clearer.

Modules are created in their own scope, which means that any variables, functions, or classes created within the module are not available externally, unless they are exported using the export keyword.

// coffeeshop.ts
module CoffeeShop {  
  export class Coffee {
    customerName: string;
    constructor(customerName : string) {
      this.customerName = customerName;
    }
    buyCoffee(price : string = '£2.50') {
      console.log('Hi, ' + this.customerName + '! Here is your coffee. That\'ll be ' + price + ', please!');
    }
  }
}

So, here we have created a very simple Coffeeshop module, using the same example from earlier. The class Coffee has been exported using export to allow the classes scope to be available externally.

// main.ts
/// <reference path="coffee.ts" />

let order1 = new CoffeeShop.Coffee('Daniel').buyCoffee();  

In another file, main.ts, we are able to call the Coffeeshop module's class to run the code, but first we must import the module to the file. This can be done by using a reference path, to locate the file in which the module was defined. We then simply call the function as before.

$ tsc *.ts --outFile main.js
$ node main.js
Hi, Daniel! Here is your coffee. That'll be £2.50, please!  

To make it easier to run, we can utilise the TypeScript compiler to combine both of these module files into one. Using the --outFile flag, we can specify the output location name. The new JavaScript file is then run, and the class behaves as expected.

This was a very simple example of using modules, and they can be much more complex, so I recommend checking out the modules documentation for more.

Further Reading

I hope this article gave a clear starting point on learning how to use TypeScript, and I'm looking to explore this further in the near future by setting up automatic build systems with Gulp/Grunt. Below are some of the great resources I used while learning TypeScript.


Originally published at danielgynn.com.

Read more posts by Daniel Gynn

Front-end developer.