JavaScript to TypeScript

TypeScript is a language written by Microsoft. The latest version of TypeScript (2.3) was released on 27/04/2017.

TypeScript is to JavaScript what which SASS and LESS are to CSS - a super-set of the language with additional functionality to improve the coding experience.

TypeScript is cross-compiled into JavaScript so that it can be used in web browsers. A map file is created to signify the relationship between the generated JavaScript, and the original TypeScript, so that it can be debugged in with browser development tools.

How does it work?

TypeScript is usually configured using a json configuration file called tsconfig.json.

When a tsconfig.json file is present in a directory it indicates the root of a TypeScript project. An example is given below:

{
	"compilerOptions": {
		"module": "system",
		"noImplicitAny": true,
		"removeComments": true,
		"preserveConstEnums": true,
		"outFile": "../../built/local/tsc.js",
		"sourceMap": true
	},
	"include": [
		"src/**/*"
	],
	"exclude": [
		"node_modules",
		"**/*.spec.ts"
	],
	"compileOnSave": true
}

The config file can be used to configure a number of parameters, including files that should be ignored when compiling from TypeScript to JavaScript (for example, it is common to exclude node_modules, such that third party libraries are not re-compiled). It also allows the configuration of compilation options, such as whether to include comments when compiling. Full details can be found here.

One option that I find particularly useful is compileOnSave. This option and its advantages should be fairly self explanatory; when changes are made it will re-compile the TypeScript to JavaScript automatically, meaning you can just reload a web page and it will have the new version, as normal with JavaScript.

After TypeScript has compiled you can reference the resultant JavaScript files from HTML, or use them in a node.js solution, just as you would with native JavaScript.

As an alternative to compilation based on tsconfig files, Webpack or Gulp can be used to bundle and minify files. and allow for further configuration options. This is particularly useful when building large single page applications using one of the leading JavaScript frameworks (e.g. Angular, Ember, React etc.).

Constructs

These constructs will help to illustrate one of the major advantages of using TypeScript over JavaScript; type safety.

Variables

In TypeScript you are able to define a specific type for your variables, this promotes type-safety as you will not be able to change its value to a different type without explicit casting.

var @string: string = 'New string';
		
//This will result in a compilation error
@string = 1;

//If you are going to define a value when initialising 
//you will not need to define the type as this will be 
//inferred

//This will be a variable wtih the type of number
var @number = 1;

//This will fail
@number = 'String';

It is important to note that there is an "any" type that can be used to allow similar duck-typing functionality to plain JavaScript.

var @variable: any = 'New string';
		
//This will no longer result in compilation error
@variable = 1;

Constants

Constants are equivalent to the newly introduced ECMAScript 2015 (JavaScript 2015) constants. It creates a read-only variable. The syntax for this is simple.

const @constant = 'Constant';
		
//This will cause a compiler error
@constant = 'Changed Constant';

Let

This is another concept that has an ECMAScript 6 counterpart. The syntax for this is very similar to that of const and var. let differs from var, in that the variable will be scoped to the current code block, overcoming an often derided feature of JavaScript, wherein var has a global reference. Further information can be found here.

let a = 100;
	
if (input) {
	// Still okay to reference `a'
	let b = a + 1;
	return b;
}

// Error: `b' doesn't exist here
return b;

Methods

In TypeScript you are able to define methods in multiple ways.

let method1 = function() {
};

let method2 = () => {
};

The second of these examples will be a familiar syntax for C# developers (being the C# Lambda syntax). Both of these methods allow you to define parameters for the constructor, along with the type that they should be, as well as a type that the method itself will return.

method1($par1: string, $par2: number): string {
	
}

method2 = ($par1: number, $par2: string) : number => {

}

Another advantage of TypeScript over previous JavaScript versions is the addition of default values for parameters, it also provides a friendly syntax for optional parameters (in JavaScript all parameters are optional by design, which has led to many a bug).

method1($par1: string, $par2: number = 1) : string {
	
}

//This would result in a compilation error, as in C#
method2($par1: string = 'String', $par2: number) : string {

}

//In this instance $par2 is optional
method3($par1: string, $par2?: number) :string{

}

Classes

Classes behave exactly as in any other language, they act as containers for properties and methods. No longer do you need to leverage functions, and the revealing module pattern, to implement class like functionality, and allow for the concept of private members.

A class is defined like so:

class MyClass {
}

As with other languages that have a class concept, TypeScript makes use of constructors to initialise properties on initialisation.

class Class {
	property1: string;

	constructor(){
		this.property1 = 'Hello World';
	}
}
  • Public - The property/method can be used outside of the class
  • Private - The property/method can only be accessed by other members in the containing class
  • Protected - The property/method can be accessed by members of it's own class, and any derived classes

If a member does not have an access modifier specified it will default to public.

In addition to explicit properties of a class, implicit properties can be created, by adding access modifiers to constructor arguments.

class Human1 {
	firstName: string;
	lastName: string;
	
	constructor($firstName: string, $lastName: string) {
		this.firstName = $firstName;
		this.lastName = $lastName;
	}
}

class Human2 {
	constructor(public firstName: string, public lastName: string) {
	}
}‚Äč

The above examples are equivalent, both create a class with public firstName and lastName properties.

Interfaces

These are similar to classes in that they act as definition of the properties and object will have. They are useful for:

  • Ensuring an object conforms to a particular structure on initialisation; and
  • Ensuring a class implements a specific set of properties or methods.

For example, in the first instance

interface IHuman {
	firstName: string;
	lastName: string;
}

//This will result in an error
var @object1 = <IHuman>{
	firstName: 'Human',
	surname: 'Person'
};

//This will not result in an error
var @object2 = <IHuman>{
	firstName: 'Human',
	lastName: 'Interface'
}

The second use:

interface IHuman {
	firstName: string;
	lastName: string;
}

//This will result in a compilation error if the properties are not present
class IncorrectHuman implements IHuman {
	firstName: string;
}

//This will not result in a compilation error
class Human implements IHuman {
	firstName: string;
	lastName: string;		
}

Interfaces

A further advantage to using TypeScript over JavaScript is that it allows the use of generics (again, a familiar concept for C# developers [C#'s influence can be found throughout TypeScript, largely because they share some of the same design team]).

interface IBase<T> {
	convertToJs(): T;
}

class Human<IHuman> {
	convertToJs(): IHuman {
		return <IHuman>{};
	}
}

class Adult<IAdult> {
	convertToJs(): IAdult {
		return <IAdult>{};
	}
}

Inheritance

Thanks to TypeScript's use of classes, it allows for a simpler implementation of inheritance. TypeScript (like JavaScript) references parent classes through the use of the super keyword:

class Human {
	firstName: string;
	lastName: string;
	
	constructor($firstName: string, $lastName: string) {
		this.firstName = $firstName;
		this.lastName = $lastName;
	}
}

class Adult extends Human {
	job: string;
	
	constructor($firstName: string, $lastName: string, $job: string) {
		super($firstName, $lastName);
		//To call the base constructor, you need to use the above it is
		//important to note that this needs to go first, ie before any further assignments
		//It is also important to note here that this call to the super construtor is required
		this.job = $job;
	}
}

In addition to providing the properties of a child class, you can also override the methods of the base class.

class Human {
	firstName: string;
	lastName: string;

	constructor($firstName: string, $lastName: string) {
		this.firstName = $firstName;
		this.lastName = $lastName;
	}

	convertToJs(): IHuman {
		return <IHuman>{
			fistName: this.firstName,
			lastName: this.lastName
		};
	}
}

class Adult extends Human {
	job: string;

	constructor($firstName: string, $lastName: string, $job: string) {
		super($firstName, $lastName);
		this.job = $job;
	}

	convertToJs(): IAdult {
		var js = <IAdult>super.convertToJs();
		js.job = this.job;
		return js;
	}
}

Structure

One of the other main advantages that TypeScript affords, besides type safety, is simpler referencing; using modules and exports.

module MyModule{
	export class TestClass {
		
	}
	
	class TestClass2 {
		
	}
}

From this module, you can reference the class TestClass as it has been exported, but you cannot reference TestClass2 (which is private to the module). Modules are similar to namespaces within C based languages, providing logical regions to scope and structure code.

class TestClass3 {
	property1: TestModule.TestClass;
	//This will fail
	property2: TestModule.TestClass2;
}

When using TestClass3, you also need to reference the file which contains TestClass and TestClass2. This is where bundling and minification can prove very useful (see How Does It Work, above).

In addition to fully referencing a class, you can use an import or require statements to import classes from external files. It is important to note that you cannot import a class unless it is exported in the dependant file.

import { TestClass2 } from './relative_path';

class TestClass3 {
	property: TestClass2;
}

I will be exploring importing and referencing in more detail in my next blog post, on Angular 2+.

Further Reading

Below are some useful resources that may be of interest:

Why do I need TypeScript?

TypeScript isn't a necessity, and you're free to program in vanilla JavaScript (the end result will be the same). TypeScript, however, has a lot of features which enforce better coding structure, less verbose code, and easier debugging.

It is for these reasons that TypeScript has had such a wide uptake, even from Microsoft's competitors (Angular 2+ is built in TypeScript).

In my next blog post I will be building on top of the concepts in this article with a discussion of Angular 2+.