TypeScript Crash-course

In this chapter we will quickly go through the most important concepts in TypeScript so that you can have a better understanding of Angular code that you will write. Knowing TypeScript definitely helps to understand Angular, but again it is not a requirement. The project files for this chapter are in angular2-intro/project-files/typescript.

TypeScript Basics

  • TypeScript is a superset of JavaScript with additional features, among which optional types is the most notable. This means that any valid JavaScript code (ES 2015/2016…) is valid TypeScript code. You can basically change the extension of the file to .ts and compile it with the the TypeScript compiler.
  • TypeScript defines 7 primary types:
    • boolean: var isDone: boolean = false;
    • number: var height: number = 6;
    • string: var name: string = "bob";
    • array: var list:number[] = [1, 2, 3]; also var list:Array<number> = [1, 2, 3];
    • enum: enum Color {Red, Green, Blue};
    • any: var notSure: any = 4;
    • void: function hello(): void { console.log('hello'); }

Interface

  • An Interface is defined using the interface keyword
  • Interfaces are used only during compilation time to check types
  • By convention, interface definitions start with an I, e.g. : IPoint
  • Interfaces are used in classical object oriented programming as a design tool
  • Interfaces don’t contain implementations
  • They provide definitions only
  • When an object implements an interface, it must adhere to the contract defined by the interface
  • An interface defines what properties and methods an object must implement
  • If an object implements an interface, it must adhere to the contract. If it doesn’t the compiler will let us know.
  • Interfaces also define custom types

Basic Interface

Below is an example of an Interface that defines two properties and three methods that implementers should provide implementations for:

 1 interface IMyInterface {
 2   // some properties
 3   id: number;
 4   name: string;
 5 
 6   // some methods
 7   method(): void;
 8   methodWithReturnVal():number;
 9   sum(nums: number[]):number;
10 }

Using the interface above we can create an object that adheres to the interface:

 1 let myObj: IMyInterface = {
 2   id: 2,
 3   name: 'some name',
 4 
 5   method() { console.log('hello'); },
 6   methodWithReturnVal () { return 2; },
 7   sum(numbers) {
 8     return numbers.reduce( (a,b) => { return a + b } );
 9   }
10 };

Notice that we had to provide values to all the properties defined by the Interface, and the implementations for all the methods defined by the Interface.

And then of course you can use your object methods to perform operations:

1 let sum = myObj.sum([1,2,3,4,5]); // -> 15

Classes as Interfaces

Because classes define types as well, they can also be used as interfaces. If you have an interface you can extend it with a class for example:

1 class Point {
2   x: number;
3   y: number;
4 }
5 interface Point3d extends Point {
6   z: number;
7 }
8 const point3d: Point3d = {x: 1, y: 2, z: 3};
9 console.log(point3d.x); // -> 1

First we are defining a class called Point that defines two fields. Then we define an interface called Point3d that extends the Point by adding a third field. An then we create a point of type point3d and assign a value to it. We read the value and it outputs 1.

Classes

  • Classes are heavily used in classical object oriented programming
  • It defines what an object is and what it can do
  • A class is defined using the class keyword followed by a name
  • By convention, the name of the class start with an uppercase letter
  • A class can be used to create multiple objects (instances) of the same class
  • An object is created from a class using the new keyword
  • A class can have a constructor which is called when an object is made from the class
  • Properties of a class are called instance variables and its functions are called the class methods
  • Access modifiers can be used to make them public or private
  • The instance variables are attached to the instance itself but not the prototype
  • Methods however are attached to the prototype object as opposed to the instance itself
  • Classes can inherit functionality from other classes, but you should favor composition over inheritance or make sure you know when to use it
  • Classes can implement interfaces

Let’s make a class definition for a car and incrementally add more things to it. The project files for this section are in angular2-intro/project-files/typescript/classes/basic-class.

Adding an Instance Variable

The Car class definition can be very simple and can define only a single instance variable that all cars can have:

1 class Car {
2   distance: number;
3 }
  • Car is the name of the class, which also defines the custom type Car
  • distance is a property that tracks the distance that car has traveled
  • Distance is of type number and only accepts number type.

Now that we have the definition for a car, we can create a car from the definition:

1 let myCar:Car = new Car();
2 myCar.distance = 0;
  • myCar:Car means that myCar is of type Car
  • new Car() creates an instance from the Car definition.
  • myCar.distance = 0 sets the initial value of the distance to 0 for the newly created car

Adding a Method

So far our car doesn’t have any definitions for any actions. Let’s define a move method that all the cars can have:

1 class Car {
2   distance: number;
3   move():void {
4     this.distance += 1;
5   }
6 }
  • move():void means that move is a method that does not return any value, hence void.
  • The body of the method is defined in { }
  • this refers to the instance, therefore this.distance points to the distance property defined on the car instance.
  • Now you can call the move method on the car instance to increment the distance value by 1:
1 myCar.move();
2 console.log(myCar.distance) // -> 1

Using Access Modifiers

If you wanted to tell the compiler that the distance variable is private and can only be used by the object itself, you can use the private modifier before the name of the property:

1 class Car {
2   private distance: number;
3   constructor () {
4     ...
5   }
6   ...
7 }
  • There are 3 main access modifiers in TypeScript: private, public, and protected:
  • private modifier means that the property or the method is only defined inside the class only.
  • protected modifier means that the property or the method is only accessible inside the class and the classes derived from the class.
  • public is the default modifier which means the property or the method is the accessible everywhere and can be accessed by anyone.

Adding a constructor

A constructor is a special method that gets called when an instance is created from a class. A class may contain at most one constructor declaration. If a class contains no constructor declaration, an automatic constructor is provided.

Let’s add a constructor to the Car class that initializes the distance value to 0. This means that all the cars that are crated from this class, will have their distance set to 0 automatically:

1 class Car {
2   distance: number;
3   constructor () {
4     this.distance = 0;
5   }
6   move():void {
7     this.distance += 1;
8   }
9 }
  • constructor() is called automatically when a new car is created
  • Parameters are passed to the constructor in the ()
  • The body of the constructor is defined in the { }

Now, let’s customize the car’s constructor to accept distance as a parameter:

1 class Car {
2   private distance: number;
3   constructor (distance) {
4     this.distance = distance;
5   }
6 }
  • On line 3 we are passing distance as a parameter. This means that when a new instance is created, a value should be passed in to set the distance of the car.
  • On line 4 we are assigning the value of distance to the value that is passed in

This pattern is so common that TypeScript has a shorthand for it:

1 class Car {
2   constructor (private distance) {
3   }
4 }

Note that the only thing that we had to do was to add private distance in the constructor parameter and remove the this.distance and distance: number. TypeScript will automatically generate that. Below is the JavaScript outputed by TypeScript:

1 var Car = (function () {
2   function Car(distance) {
3     this.distance = distance;
4   }
5   return Car;
6 })();

Now that our car expects a distance we have to always supply a value for the distance when creating a car. You can define default values if you want so that the car is instantiated with a default value for the distance if none is given:

1 class Car {
2   constructor (private distance = 0) {
3   }
4   getDistance():number { return this.distance; }
5 }

Now if I forget to pass a value for the distance, it is going to be set to zero by default:

1 const mycar = new Car();
2 console.log(mycar.getDistance()); //-> 0

Note that if you pass a value, it will override the default value:

1 const mycar = new Car(5);
2 console.log(mycar.getDistance()); //-> 5

Setters and Getters (Accessors)

It is a very common pattern to have setters and getters for properties of a class. TypeScript provides a very simple syntax to achieve that. Let’s take our example above and add a setter and getter for the distance property. But before that we are going to rename distance to _distance to make it explicit that it is private. It is not required but it is a common pattern to prefix private properties with an underscore.

1 class Car {
2   constructor (private _distance = 0) {}
3   getDistance():number { return this._distance; }
4 }

In order to create the getter method, we are going to use the get keyword and the name for the property followed by ():

1 class Car {
2   constructor (private _distance = 0) {}
3   get distance() { return this._distance; }
4 }

Now we can get the value of distance:

1 const car2 = new Car(5);
2 console.log(car2.distance) //-> 5

Note on line 2 that we didn’t call a function. Behind the scenes, TypeScript creates a property for us, that’s why it is not a method. Below is the relevant generated JavaScript:

1 Object.defineProperty(Car.prototype, "distance", {
2   get: function () { return this._distance; },
3   enumerable: true,
4   configurable: true
5 });

JavaScript behind the scenes calls the get function for you to get the value, and that’s why we simply did car2.distance as opposed to car2.distance(). For more information about Object.defineProperty checkout the MDN docs.

Similar to the getter, we can define a setter as well:

1 class Car {
2   constructor (private _distance = 0) {}
3   get distance() { return this._distance; }
4   set distance(newDistance: number) { this._distance = newDistance; }
5 }

Now we can both get and set the distance value:

1 const coolCar = new Car();
2 console.log(coolCar.distance); // -> 0
3 
4 coolCar.distance = 55;
5 console.log(coolCar.distance); // -> 55

Note that if we take out the setter, we won’t be able to assign a new value to distance.

Static Methods and Properties

Static methods and properties belong to the class but not the instances. For example, the Array.isArray method is only accessible through the Array but not an instance of an array:

1 var x = [];
2 x.isArray // -> undefined
3 Array.isArray(x) //-> true
  • On line 2 we are trying to access the isArray method, but obviously it is not defined because isArray is a static method.
  • On line three we are calling the static isArray method from Array and we can check if x is an array.

If you look at the Array documentation you can see that methods and properties are either defined on the Array.prototype or Array:

  • Array.prototype.x: makes x available to all the instances of Array
  • Array.x: x is static and only available through Array.

Now that we have some context, let’s see how you can define static methods and properties in TypeScript. Consider the code below:

 1 class Car {
 2   static controls: {isAuto: boolean } = {
 3     isAuto: true
 4   };
 5   static isAuto():boolean {
 6     return Car.controls.isAuto;
 7   }
 8   constructor (private _distance = 0) {}
 9   get distance() { return this._distance; }
10 }
11 
12 console.log(Car.controls); // -> { isAuto: true }
13 console.log(Car.isAuto()); // -> true
  • On line 2 we are defining a static property called controls using the static modifier. Then we specify the form and then assign a value for it.
  • On line 5 we are defining a static method called isAuto using the the static modifier. This method simply returns the value of isAuto from the static control object. Not that we get access to the class using the name of the class as opposed to using this. i.e. return Car.controls.isAuto

Implementing an Interface

Classes can implement one or multiple interfaces. We can make the Car class implement two interfaces:

1 interface ICarProps {
2   distance: number;
3 }
4 interface ICarMethods {
5   move():void;
6 }

Making the Car class implement the interfaces:

1 class Car implements ICarProps, ICarMethods {
2   distance: number;
3   constructor () {
4     this.distance = 5;
5   };
6   move():void {
7     this.distance += 1;
8   };
9 }

The above example is silly, but it shows the point that a class can implement one or more interfaces. Now if the class does not provide implementations for any of the interfaces, the compiler will complain. For example, if we leave out the distance instance variable, the compiler will print out the following error:

error TS2420: Class ‘Car’ incorrectly implements interface ‘ICarProps’. Property ‘distance’ is missing in type ‘Car’.

Inheritance

In Object-oriented programming, a class can inherit from another class which helps to define shared attributes and methods among objects. Although this pattern is very useful, it should be used cautiously as it can lead to code that is hard to maintain. You can learn more about classical inheritance and prototypical inheritance by watching Eric Elliot’s talk at O’Reilly’s Fluent Conference. The project files for this section are in angular2-intro/project-files/typescript/classes/inheritance.

Let’s get started by creating a base class called Vehicle. This class is going to be the base class for other classes that we create later.

 1 // Vehicle.ts
 2 export class Vehicle {
 3   constructor( private _name: string = 'Vehicle',
 4                private _distance: number = 0 ) { }
 5   get distance(): number { return this._distance; }
 6   set distance(newDistance: number) { this._distance = newDistance; }
 7   get name(): string { return this._name;}
 8   set name(newName: string) { this._name = newName; }
 9   move() { this.distance += 1 }
10   toString() { return this._name; }
11 }

There is nothing special in this class. We are just creating a class that has two private properties (name, distance) and we are creating the setters and getters for them. Additionally, we are defining the toString method that JavaScript internally calls in “textual contexts”. The constructor is the most notable of all the other methods:

  • It sets the name property to “Vehicle” for all the instances
  • It also sets the distance property to 0.

This means that when a class extends the Vehicle class, it will have to call the constructor of Vehicle using the super keyword. Let’s do that now by creating two classes called Car and Truck that inherit from the Vehicle class:

cars.ts

 1 import {Vehicle} from './vehicle';
 2 export class Car extends Vehicle {
 3   constructor(name?: string) {
 4     super();
 5     this.name = name || 'Car';
 6   }
 7 }
 8 export class Truck extends Vehicle {
 9   constructor(name?: string) {
10     super();
11     this.name = name || 'Truck';
12   }
13 }
  • The Car class and the Truck class both look almost identical. They both inherit from the Vehicle using the extends keyword.
  • They both call the Vehicle’s constructor in their own constructor method before implementing their own: constructor(name?: string) { super(); }
  • They both take an optional name property to set the name of the vehicle. If not name is provided, it will be set to either ‘Car’ or ‘Truck’

Now let’s create the main file and run the file:

 1 import {Car, Truck} from './cars';
 2 
 3 /**
 4  * Creating a new car from `Car`
 5  */
 6 const car = new Car();
 7 console.log(car.name);
 8 car.distance = 5;
 9 car.move();
10 car.move();
11 console.log(car.distance);
12 /**
13  * Creating a new Truck.
14  */
15 const truck = new Truck();
16 console.log(truck.name);
  • On line 1 we are importing the Car and the Truck class.
  • and then we create a Car and Truck instance and log their names and distance to the console.

Run the build task (command + shift + b) and run the file (F5) and you should see the output:

1 node --debug-brk=7394 --nolazy output/main.js
2 Debugger listening on port 7394
3 Car
4 7
5 Truck

You can play around with the code above an try passing a string when instantiating a Car or a Truck to see the name change.

TODO

  • constructor overloading

Class Decorators

There are different types of decorators in TypeScript. In this section we are going to focus on Class Decorators.

TODO

add content

Modules

  • In TypeScript you can use modules to organize your code, avoid polluting the global space, and expose functionalities for others to use.
  • Multiple modules can be defined in the same file. However, it makes more sense to keep on module per file
  • If you want, you can split a single module across multiple files
  • If you decide to split a module across different files, this is how you would do it:
    • Create the module file: mymodule.ts and declare your module there: module MyModule {}
    • Create another file: mymodule.ext1.ts and on top of the file add: /// <reference path="mymodule.ts" />. Then in the file, you can use the same name of the module and add more stuff to it: module MyModule { // other stuff... }
    • Then in your main file, you need two things on top of the file:
      • /// <reference path="mymodule.ts" />
      • /// <reference path="mymodule.ext1.ts" />
    • Then, you can use the name of your module to refer to the symbols defined: MyModule.something, MyModule.somethingElse
  • TypeScript has two system: one used internally and the other used externally
  • External modules are used if your app uses CommonJS or AMD modules. Otherwise, you can use TypeScript’s internal module system
  • Using TypeScript’s internal module system, you can:
    • use the module keyword to define a module: module MyModule { ... }
    • split modules into different files that contribute to a single module
    • use the /// <reference path="File.ts" /> tag to tell the compiler how files are related to each other when modules are split across files
  • Using TypeScript’s external module system:
    • you cannot use the module keyword. The module keyword is used only by the internal module system.
    • instead of the reference tag, you can use the import keyword to define the relationship between modules
    • you can import symbols using the file name: import mymodule = require('mymodule')

The project files for this chapter are in angular2-intro/project-files/typescript/modules.

Simple Module

Let’s create a simple module that contains two classes. The first class is a vehicle class and the second is a car class that inherits from the vehicle class. Then we are going to expose the car class to the outside world and import it from another file. The project files for this section are in angular2-intro/project-files/typescript/modules/basic-module.

First, create the main.ts file and copy paste the following:

main.ts

1 module MyModule {
2   class Vehicle {
3     constructor (public name: string = 'Vehicle', private _distance: number = 0)\
4  {}
5     get distance():number { return this._distance; }
6     set distance(newDistance: number) { this._distance = newDistance; }
7     move() { this.distance += 1 }
8   }
9 }
  • On line 1 we are defining the module called MyModule.
  • Inside this module we have defined a class called Vehicle that has a distance property and a setter and getter.

Now we want to create a class and export it so that it can be imported by others:

main.ts

 1 module MyModule {
 2   class Vehicle {
 3     constructor (public name: string = 'Vehicle', private _distance: number = 0)\
 4  {}
 5     get distance():number { return this._distance; }
 6     set distance(newDistance: number) { this._distance = newDistance; }
 7     move() { this.distance += 1 }
 8   }
 9   // -> adding the car class
10   export class Car extends Vehicle {
11     constructor (public name: string = 'Car') {
12       super();
13     }
14   }
15 }
  • On line 9 we are using the export keyword to indicate that the Car class is exposed and can be used by others.

Now, let’s create a car using the Car class defined in the MyModule module:

1 const mycar = new MyModule.Car('My Car');
2 console.log(mycar.name);

Note that we accessed the Car class using the MyModule symbol: MyModule.Car. Now we can split up the module into its own file and import it into the main file. Let’s create a file called MyModule.ts and move the module definition to that file. Now in our main file we are just going to import the module and use the car class from it.

main.ts

1 /// <reference path="MyModule.ts" />
2 const mycar = new MyModule.Car('My Car');
3 console.log(mycar.name);

Note that we can create an alias to the MyModule using import AliasName = MyModule. Now you can reference the module name with AliasName:

1 /// <reference path="MyModule.ts" />
2 import AliasName = MyModule;
3 const mycar = new AliasName.Car('My Car');
4 console.log(mycar.name);

Now if we run this in debug mode, the compiler will complain that it can’t find the MyModule reference. Because of that we need to make some changes to our config files. First, we are going to add the out property in the tsconfig.json file. This will tell the compiler to compile all the files into a single file:

1 "out": "output/run.js",

So our tsconfig.json file will look like this:

 1 {
 2   "compilerOptions": {
 3     "experimentalDecorators": true,
 4     "emitDecoratorMetadata": true,
 5     "module": "commonjs",
 6     "target": "es5",
 7     "sourceMap": true,
 8     "outDir": "output",
 9     "out": "output/run.js",
10     "watch": true
11   }
12 }

Now if you run the build, you should see that all the project has been compiled into output/run.js. In addition to the tsconfig.json file, we are going to update the launch.json file and add a new configuration field:

1 {
2   "name": "TS All Debugger",
3   "type": "node",
4   "program": "output/run.js",
5   "stopOnEntry": false,
6   "sourceMaps": true
7 }

Now we should be able to use the debugger and put breakpoints in our TypeScript files. Select TS All Debugger from the debugger dropdown and run the debugger and it should stop if you put a breakpoint in any of your TypeScript files.

NOTE Using the configuration files above we can compile all the TypeScript files into a single JavaScript file. But sometimes that is not what you want. Be aware that using the above configuration you will not get an output for each TypeScript file.

Splitting Internal Modules

Internal modules in TypeScript are open ended. This means that you can define a module with the same name in different files and keep adding to it. This is also known as merging. In this section we are going to demonstrate merging multiple files that contribute to a single module called Merged. The project files for this section are in angular2-intro/project-files/typescript/modules/merged-module.

First, we are going to make two files: A.ts and B.ts. In each file we are going to define the Merged module:

1 // A.ts
2 module Merged {
3   const name = 'File A'; // not exported
4   export class Door {
5     constructor (private _color = 'white') {}
6     get color() { return this._color; }
7     set color(newColor) { this._color = newColor; }
8   }
9 }

and then the B.ts file:

1 // B.ts
2 module Merged {
3   const name = 'File B'; // not exported
4   export class Car {
5     constructor(public distance = 0) {}
6     move () {this.distance += 1;}
7   }
8 }

We just created two files called A.ts and B.ts and each file we defined the Merged module and added a class to each and exported it. Now we are going to make the main.ts file and reference these two files:

1 // main.ts
2 /// <reference path="./A.ts" />
3 /// <reference path="./B.ts" />

And now we can use the classes defined in the Merged module, that is the Car and the Door class:

1 /// <reference path="./A.ts" />
2 /// <reference path="./B.ts" />
3 const car: Merged.Car = new Merged.Car();
4 const door: Merged.Door = new Merged.Door();
5 door.color = 'blue';
6 car.move();
7 car.move();
8 console.log(car.distance);
9 console.log(door.color);

if you run the build task (command + shift + b) and hit F5 you should see the following output:

1 node --debug-brk=19237 --nolazy output/run.js
2 Debugger listening on port 19237
3 2
4 blue

External Modules

In addition to TypeScript’s internal module system, you can use external modules as well. In this section we are going to demonstrate how you can use external modules in TypeScript. The project files for this section are in angular2-intro/project-files/typescript/modules/external-module.

Let’s say I have a JavaScript Node module defined in CommonJS format in a file called common.js:

1 // common.js
2 module.exports = function () {
3   this.name = 'CommonJS Module';
4 };

In order to import this we need to do two things: first, we need to install Node’s Type Definitions. Then we need to require the module. To install Node’s Type Definitions run the following the terminal in the root of your project:

1 tsd install node --save

Now you should see a folder called typings containing the type definitions. Now that we have Node’s type definitions, let’s add a reference to it on top of main.ts:

1 // main.ts
2 /// <reference path="./typings/node/node.d.ts" />

and then we are going to require the module and log it to the console:

1 // main.ts
2 /// <reference path="./typings/node/node.d.ts" />
3 const common = require('./common');
4 console.log(common()); // --> CommonJS Module

After running the build task ( command + shift + b ), and running the file (F+5) you should see the following output:

1 node --debug-brk=32221 --nolazy run.js 
2 Debugger listening on port 32221
3 CommonJS Modules

Note the configuration files that we are using:

tsconfig.json

 1 {
 2   "compilerOptions": {
 3     "experimentalDecorators": true,
 4     "emitDecoratorMetadata": true,
 5     "module": "commonjs",
 6     "target": "es5",
 7     "sourceMap": true,
 8     "outDir": "output",
 9     "out": "run.js",
10     "watch": true
11   }
12 }

launch.json

 1 {
 2   "version": "0.1.0",
 3   "configurations": [
 4     {
 5       "name": "TS All Debugger",
 6       "type": "node",
 7       "program": "./run.js",
 8       "stopOnEntry": false,
 9       "sourceMaps": true
10     }
11   ]
12 }

Decorators

  • Decorators can be used to add additional properties and methods to existing objects.
  • Decorators are a declarative way to add metadata to code.
  • There are four decorators: ClassDecorator, PropertyDecorator, MethodDecorator, ParameterDecorator
  • TypeScript supports decorators and does not know about Angular’s specific annotations.
  • Angular provides annotations that are made with decorators behind the scenes

Method Decorators

Goals: - make a method decorator called log. - Decorate someMethod in a class using @log

1 class SomeClass {
2   @log
3   someMethod(n: number) {
4     return n * 2;
5   }
6 }

In the usage, someMethod has been decorated with log using the @ symbol. @log is decorating someMethod because it is placed right before the method.

  • Decorator Implementation:
 1 function log(target: Function, key: string, value: any) {
 2   return {
 3     value: function (...args: any[]) {
 4       var a = args.map(a => JSON.stringify(a)).join();
 5       var result = value.value.apply(this, args);
 6       var r = JSON.stringify(result);
 7       console.log(`Call: ${key}(${a}) => ${r}`);
 8       return result;
 9     }
10   };
11 }

A method decorators takes a 3 arguments:

  • target: the method being decorated.
  • key: the name of the method being decorated.
  • value: a property descriptor of the given property if it exists on the object, undefined otherwise. The property descriptor is obtained by invoking the Object.getOwnPropertyDescriptor function.

TODO

  • Add decorator content for each type.