Friday, September 8, 2017

Typescript Fundamentals

Introduction :

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.

TypeScript -----------> Transpile ------------>Javascript

It has features like
  - Strongly Typed
  - Object Oriented (Class, Interface etc..)
  - Support for Compile time error with cool tools (VS Code editor)

Installation

For Mac:

$ sudo npm install -g typescript

$ tsc --version
Version 2.5.2

Editor :

Use VS Code editor to see compile time error (https://code.visualstudio.com/)
After the installation, open the editor and if you are in Mac type
Shift + Command + P
Search for Path, and Install
This will help you to open the editor from command line.

How to run TS file ?

$ mkdir ts-example
$ cd ts-example/
ts-example raja$ code main.ts

It will open the VS Code editor with the file main.ts . Here 'ts' stands for typescript

main.ts:
function write(message) {
    console.log(message);
}
var message = 'Hello World';
write(message);

Compile:
ts-example raja$ tsc main.ts 
ts-example raja$ ls
main.js main.ts

main.js:
function write(message) {
    console.log(message);
}
var message = 'Hello World';
write(message);

* Both ts and js code are same.

Run:
ts-example raja$ node main.js

Hello World

Variable

function someWork() {
    for (var i = 0; i < 5; i++) {
        console.log(i);
    }
    console.log('Finally : ' + i);
}
someWork();

Run :
$ tsc main.ts 
$ node main.js
0
1
2
3
4
Finally : 5

So var i is available outside of the for loop i.e to the nearest function someWork.
Use let instead of var :

function someWork() {
    for (let i = 0; i < 5; i++) {
        console.log(i);
    }
    console.log('Finally : ' + i); //Compile Error in VS Code
}
someWork();

One can find the compile time error. Check the editor.

* let has the scope to the block rather to the nearest function.

$ ls
main.js main.ts
$ rm main.js
$ tsc main.ts 
main.ts(5,32): error TS2304: Cannot find name 'i'.

But still created the js file.

$ ls
main.js main.ts

TS has to be compiled to ES5 JS file and ES5 doesn’t have let keyword. So it will convert the let keyword to var.

$ cat main.js
function someWork() {
    for (var i = 0; i < 5; i++) {
        console.log(i);
    }
    console.log('Finally : ' + i);
}
someWork();

Types:

let count = 1; //Its a number
count = 'a'; //Compile type error, cant assign string to a number variable
let a; //Type any, it can take any type
a = 1; //No error 
a = "hello"; //No error 
a = false; //No error 

* So better to give a type to a variable

let a: number; //Type number
a = 1; //No error
a = "hello"; //Compile time error
a = false; //Compile time error

Other Types:

let a: number;
let b: boolean;
let c: string;
let d: any; //Store any type
let e: number[];
let f: number[] = [1,2,3]; //Initialized number array
let g: any[] = [1, "hello", true]; //Array to store any type

ENUM

TS has Enum to store constants

enum Color {
    Red = 0,
    Blue = 1,
    Green = 2
};
let bgColor = Color.Blue;

Type Assertion:

let message = 'xyz';
let mEndsWith = message.endsWith('z'); 

VS Code editor suggest methods after the dot operator as it knows message type which is a string

let message1; //Any type
message1 = 'hello';
//message1. <wont suggest anything as the editor does not know the type of message1>

let m1EndsWith = (<string>message1).endsWith('o'); //Only help in compile time to find the method associated by casting

let m1EndsWith1 = (message1 as string).endsWith('o');//Another way

Arrow functions

//Normal function definition
let printData = function(inputData) {
    console.log(inputData);
}

Arrow function : Remove the function keyword and use => as below

//Single Parameter
let printData1 = (inputData) => {
    console.log(inputData);
}
//Other way, remove the curly braces if it is a single line
let printData2 = (inputData) => console.log(inputData);
//Another way, even parenthesis can be removed if only single input parameter 
let printData3 = inputData => console.log(inputData);

//No parameter
let printData4 = () => {
    console.log();
}
let printData5 = () => console.log();


Custom Type :

Interface

Check the following function

//Pass parameter as object
let drawMorePoint1 = (point) => {
    //....draw
}
drawMorePoint1({x:1,y:2});

However if we pass string as parameter we won't get any compile time error but the code will break

drawMorePoint1({x:'name',y:2});

Solution -1 
Define inline annotation. Define what point is expecting, in our case 2 params x, y and both should be number. However if we pass string then we can find the compile time error in editor.

let drawMorePoint2 = (point: {x: number, y: number}) => {
    //....draw
}
drawMorePoint2({
    x:'1', //compile time error
    y:2
});

Inline annotation is difficult to use in all the places. Every time we have to repeat this in other places and if more no of arguments are present then its hard to read. 
So lets bring the concept called Interface

Solution -2

Create a interface Point which has 2 field definition x, y that stores number.
Use Point as type for the object point in the method drawMorePoint3

interface Point {
    x: number,
    y: number
}
let drawMorePoint3 = (point : Point) => {
    //....draw
}

//If we pass other type
drawMorePoint3({
    x:'1', //compile time error
    y:2
});
//No error if number is passed
drawMorePoint3({
    x:1,
    y:2
});

However we are breaking cohesive rule with above structure. Meaning, the related method (drawMorePoint3) is not part of the interface. Its outside of it.

Can we add function implementation inside a interface ? 
We can only add the function declaration but not the implementation.

//Interface can not have function implementation but only declaration
interface Point {
    x: number,
    y: number,
    draw: () => void //empty function return void
}
//Here function is related to Interface but not part of it. 
let drawMorePoint = (point : Point) => {
    //....draw
}
So to make the cohesiveness work lets bring the concept called Class.

Class 

class Point {
    x: number; //Fields
    y: number;
    draw() {
        console.log('X: ' + this.x + ' Y: ' + this.y);
    }
    getDistance(anotherPoint: Point) {
        //...
    }
}

In the example above  draw()and  getDistance(anotherPoint: Point)methods which are part of the unit. Its not hanging outside and polluting the global space.

*One way of creating object
let point: Point = new Point(); //allocate memory 

*Another clean way of creating object.Type declaration not needed
let point = new Point(); 

*Accessing Fields
point.x = 1;
point.y = 2;
point.draw();

How to initialize an Object ?

Using Constructor keyword

class Point {
    x: number;
    y: number;
    constructor(x?: number, y?: number) {
        this.x = x;
        this.y = y;
    }
    draw() { 
        console.log('X: ' + this.x + ' Y: ' + this.y);
    }
}
let point = new Point(1, 2);
point.draw();
let point1 = new Point();
point1.draw();

Example above adding '?' as prefix to field variable in constructor makes parameter optional.
No concept of constructor overloading in TS.

Access Modifier

3 types :
  -- public
  -- private 
  -- protected

class Point {
    private x: number;
    private y: number;
    constructor(x?: number, y?: number) {
        this.x = x;
        this.y = y;
    }
    draw() { //default it is Public but one can add Public as Prefix
        console.log('X: ' + this.x + ' Y: ' + this.y);
    }
}
let point = new Point(1, 2);
point.x = 5; //Compile error, property is private
point.y = 7; //Compile error, property is private
point.draw();

Clean code :

Don’t need to add private field. It can be defined in constructor itself. Constructor automatically create and assign passed value to the fields.

class Point {
    constructor(private x?: number, private y?: number) {
    }
    draw() {
        console.log('X: ' + this.x + ' Y: ' + this.y);
    }
}
let point = new Point(1, 2);
point.draw();

How to Access private data ?

Add getter and Setter functions : 

class Point {
    constructor(private x?: number, private y?: number) {
    }
    draw() {
        console.log('X: ' + this.x + ' Y: ' + this.y);
    }
    getX() {
        return this.x;
    }
    setX(value) {
        if (value < 0) {
            throw new Error('Value can not be less than 0')
        }
        this.x = value;
    }
}
let point = new Point(1, 2);
let x = point.getX();
point.setX(10)
point.draw();

Instead of get and set function TS has a better approach below.

class Point {
    //Use underbar so not to have any issues with property name (getter and setter)
    constructor(private _x?: number, private _y?: number) {
    }

    draw() {
        console.log('X: ' + this._x + ' Y: ' + this._y);
    }

    get x() { //get, space then property name
        return this._x;
    }

    set x(value) { //set, space then property name
        if (value < 0) {
            throw new Error('Value can not be less than 0')
        }
        this._x = value;
    }
}
let point = new Point(1, 2);
let x = point.x //Just use as field not as function
point.x = 10;
point.draw();

Note :
Property looks like a field from outside but internally its a method in a class more specifically getters and setters.

Modules

Modules helps TS to organize the code in different files. Check the example below.
Create another file called point.ts and moved the class definition.

point.ts :

Use export keyword to open the visibility of the class to other files

export class Point {
    constructor(private _x?: number, private _y?: number) {
    }
    draw() {
        console.log('X: ' + this._x + ' Y: ' + this._y);
    }
}

main.ts :

In the example below I mention the Class name Point (between curly braces) which I want to import.
We don't need to add .ts as suffix in the module name from which we are importing.

import {Point} from './point';

let point = new Point(1, 2);
point.draw();

2 comments:

  1. Hi, Great.. Tutorial is just awesome..It is really helpful for a newbie like me.. I am a regular follower of your blog. Really very informative post you shared here. Kindly keep blogging. If anyone wants to become a Front end developer learn from TypeScript Training in Chennai

    ReplyDelete