Typescript barcelona

36
Typescript fundamentals Christoffer Noring, Ovo Energy

Transcript of Typescript barcelona

Typescript fundamentals

Christoffer Noring, Ovo Energy

chris_noring

Christoffer Noring, Ovo EnergyGoogle Developer Expert

Types – NO types and the problemWhy types? - It adds clarity to your code - It gives you compile time errors if you mispel or use the wrong type

function doStuff() { console.log('do stuff')}

function test() { var a = dostuff();}

test();

Mispelling detected in runtime

function add(a, b) { return a + b;}

add(1, "test"); // '1test' works but NOT want you want

add(1,2) // 3, does what you want

Unclear types, might use wrong

1

2

Types in typescript• Boolean• Number• String• Any

var a = 3; // implicit declaration

a = 'string'; // will fail in compilation

var str = 'a string';str = false; // will fail in compilation

any

number boolean string

var anyType: any = 5;

anyType = ' go crazy '; // DON‘T do this even if you can !!

var b: number = 5; b = 7; // WORKS!!

Types exampleYou can give types to more than variables- Input parameters- Function returns

function add(a, b) { return a + b;}

function addWithTypes(a: number, b: number): number { return a + b;}

add(1, "a string"); // allowedadd(1, 2); // allowed

addWithTypes(1, 1); // allowed

// compile time erroraddWithTypes(1, "a string");

Parameters

Return type

Let and constThe reason for let existing is the lack of a proper block scope. We ONLY have function scope

var a = 3;

while(true) { var a = 5; // redefines ’a’}

console.log(a); // prints 5, probably not what we want

let a = 7;

while (true) { let a = 5;}

console.log(a);

Converting to typescript

Resulting es5 code

var a = 7;while (true) {

var a_1 = 5;}console.log(a);

It renames the innervariable

Const

const x = 3;

x = 5; // compilation error

1

2

3

Enum and typesenum ProductTypes { Books, Movies, Other}

Only numbers

type Cars = 'Ferrari' | 'Volvo' | 'Porsche';

var example: Cars;

example = 'Ferrari';

example = 'Saab';

Restrict what it can be with type

// Correct

// IncorrectProductTypes.Books // = 0

Template stringsvar baseUrl = 'ourdomain/app';var id = 5;

var url = baseUrl + '/products/' + id;

let betterUrl = `${baseUrl }/products/${id}`;

console.log(url);

console.log(betterUrl);

- Hard to read- Error prone

- Easier to read- Less error prone

Backtick ` and ${ variable }

Rest operator

function sum(...numbers: Array<number>) { let amount = 0;

numbers.forEach(function (num) { amount += num; });

return amount;}

sum(1, 2, 3, 4);sum(1, 2);

An unknown number of arguments all collected in the arraynumbers

function sum() { var numbers = []; for (var _i = 0; _i < arguments.length; _i++) { numbers[_i - 0] = arguments[_i]; } var amount = 0; numbers.forEach(function (num) { amount += num; }); return amount;}

sum(1, 2, 3, 4);sum(1, 2);

Uses built in arguments, no surprise there

For OfWhat problem does it solve?

Using for- in loops the keys by Object.keys rather than looping the items

var array = [1, 2, 3, 4];

for (item in array) { console.log(item);}

Loops out ”0”, ”1”, ”2”

var array = [1, 2, 3, 4, 5],for( let item of array ) {

console.log(item);}

Using for-ofvar array = [1, 2, 3, 4, 5];for (var _i = 0, array_1 = array; _i < array_1.length; _i++) { var item = array_1[_i]; console.log(item);}

Converts it to a normal for-loop that DOESN’T loop keys1 2

Default valuesfunction test(a: number = 2, b: string = 'test') { console.log('do stuff');}

function test(a, b) { if (a === void 0) { a = 2; } if (b === void 0) { b = 'test'; } console.log('do stuff');}

Becomes

Checks if it is set, if no then set it to value you decided

function test( config = mandatory() ) {

}

function mandatory() { throw ' config missing ';}

function test(config) { if(!config) { throw 'config needed' }}

Classes - basicsclass Person { name: string;

constructor(dto) { this.name = dto.name; }

getName() { return this.name; }

}

var person = new Person({ name: 'Sergio' });

Fields declared

Constructor keyword

Method without keyword function

var Person = (function () { function Person(dto) { this.name = dto.name; } Person.prototype.getName = function () { return this.name; }; return Person;} ());var person = new Person({ name: 'Sergio' });

Self executing function with a closureMethod is on prototype as expected

Classes – generated fields class Controller { constructor( private service1: Service1, private service: Service2, public val: number ) {

}}

class Service1 {}

class Service2 {}

let controller = new Controller( new Service1(), new Service2(), 3 );

controller.val = 4;

controller.service1 // compile time error

var Controller = (function () { function Controller(service1, service, val) { this.service1 = service1; this.service = service; this.val = val; } return Controller;} ());var Service1 = (function () { function Service1() { } return Service1;} ());var Service2 = (function () { function Service2() { } return Service2;} ());var controller = new Controller(new Service1(), new Service2(), 3);controller.val = 4;controller.service1; // compile time error

Automatic creation and assigning of fields

Classes inheritanceclass Shape { constructor(private x: number, private y: number) {

}

move(dx: number, dy: number) { }}

class Rectangle extends Shape { constructor(x: number, y: number) { super(x, y); }

move(x: number, y: number) { // move like a rectangle }}

var __extends = (this && this.__extends) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());};var Shape = (function () { function Shape(x, y) { this.x = x; this.y = y; } Shape.prototype.move = function (dx, dy) { }; return Shape;} ());var Rectangle = (function (_super) { __extends(Rectangle, _super); function Rectangle(x, y) { _super.call(this, x, y); } Rectangle.prototype.move = function (x, y) { // move like a rectangle }; return Rectangle;} (Shape));

- Defines an __extends method- Rectangle’s prototype equals Shape- Creates an instance from Shape- All 1 st level methods in Shape are copied to Rectangle

Call base class constructor as Rectangle

Fat arrowsThe problem – looses track of this

function Test(cb) { this.a = 3;

setTimeout(function () { //do work this.a = 5; cb(); }, 4000);

}

var test = new Test(function () { console.log(test.a);});

These two this, points to different places, inner assignment DON’T do what you think

Solution – use fat arrow

function Test(cb) { this.a = 3;

setTimeout(() => { //do work this.a = 5; cb(); }, 4000);

}

var test = new Test(function () { console.log(test.a);});

function Test(cb) { var _this = this; this.a = 3; setTimeout(function () { //do work _this.a = 5; cb(); }, 4000);}var test = new Test(function () { console.log(test.a);});

Compare var this = that

Called an ”arrow”

Interfacesinterface IService { repo: any;

doStuff(x: number);

class Test implements IService { repo;

doStuff(y: number) {

}}

Can have fields AND methods as part of the contract

{}var Test = (function () { function Test() { } Test.prototype.doStuff = function (y) { }; return Test;} ());

Interface becomes NOTHING when compiled, only thereduring compilation

Interface - magicinterface IService { repo: any;

doStuff(x: number){ }}

var instance = <IService>{};

instance.doStuff = function () {

};

instance.repo = 'bla';

Is possible to create instance from an interface !!!

We have mocking built in ☺

But you need to declare all the properties methods yourselves,

easy to miss a property though.. WARNING

Destructuring – I am too lazy to write the whole namevar rect = { x: 0, y: 10, width: 15, height: 20 };

var { x, y, width, height} = rect;

class Person {

constructor( private name: string = "Chris", private age: number = 36) { }

getName() { return this.name; }

toString() {

}}

var person = new Person();

var { name, age } = person;

console.log(name, age); // chris, 36

console.log(x, y, width, height); // 0,10,15,20

Pick out the properties

Refer to name, age instead of person.name or person.age

var person = new Person();var name = person.name, age = person.age;console.log(name, age); // chris, 36

PromisePromises are from es6 and needs to be either installed from typings or tsd or your comilation target needs to be es6

function getData() {return new Promise((resolve, reject) => {

setTimeout(() => { resolve('some data') }, 3000);

});}

Some time in the future the data will be ready to be returned

We return straight away but its first after we call resolve or reject thatthere is data to be had

If reject is called, then something went wrong..

getData() .then((data) => { // do stuff }) .catch(() => { // handle error })

1

Consume2

Declare

Description files – working with non typescript libs

A lot of libs are written are written in es5

We want to be able to use them in our typescript project

BUT

We want types – answer is description files

Definitely typed is a large repo for most known 3rd party libs out there, http://definitelytyped.org/Description files can be downloaded from there using a tool called tsd or the new typings

Description files - how does it look and work?

1 Create a file <es5 filename>.d.ts

3 Refer to that file in your ts project, thats it

2 Explain to typescript on a meta level how your es5 constructs should be interpreted

Description files – describe a classfunction Mathematic() {

}

Mathematic.prototype.add = function (a, b) { return a + b;}

Mathematic.prototype.sub = function (a, b) { return a - b;}

Mathematic.PI = 3.14;

declare class Mathematic { static PI: number;

new(): Mathematic; add(a: number, b: number): number

sub(a: number, b: number): number;}

A function with a constructor class becomes, well a class

No implementation, just description

constructor

Description files – common constructs function doStuff(a, b) { return 5;}

function complexFunction(name, config) { if (name === 'pele') { console.log('Brazil'); }

if ( config && config.length && config.game && config.game === 'Football') { console.log(’Messi is great'); }}

var Singleton = (function () { return { save: save, update: update }

function save() {

}

function update() {

}

})();

declare function doStuff( a: number, b: number ): number;

interface IConfig { game?: string, length?: number}

declare function complexFunction( name: string, config?: IConfig );

interface ISingleton { save(); update();}

declare var Singleton: ISingleton;

1

2

3

Compiler tsc <filename>.ts

tsc <filename>.ts <filename>.ts

tsc <filename>.ts <filename>.ts --out <resulting file>.js

tsc <filename>.ts -w

tsc <filename>.ts <filename>.ts --modules amd | system

tsc <filename>.ts<filename>.ts -sourcemap--out<resulting file>.js

Concatenate into one file

Watches and recompiles on changeCompiles into one filewhen it is split up in modules

Gives it sourcemaps

tsconfig.jsonInstead of typing it all on the command line you can specify it all in json so commandline is only

tsc

{ "compilerOptions" : { "outFile": "out.js", "target": "es5", "removeComments": true, "sourceMap": true }, "files": [ "code.ts", "other.ts" ]}

Small app, powerful commands

Concatenated output

Sourcemap

All the files to compile

Bigger projects, usually gulp/grunt

ModulesYou need to split your project in smaller files to make it maintainable. Currently there are three differentoptions to do this

--module flag and compile for either amd or system

Browserify + tsify to make it work for commonjs

1

3

2

1

/// <reference path="./other.ts" />namespace App { var point = new Shapes.Point(); console.log(point.x);

}

namespace App.Shapes { export class Point {

constructor( public x= 0, public y= 0 ) { } }

}

tsc app.ts other.ts –out app.js

var App;(function (App) { var Shapes; (function (Shapes) { var Point = (function () { function Point(x, y) { if (x === void 0) { x = 0; } if (y === void 0) { y = 0; } this.x = x; this.y = y; } return Point; } ()); Shapes.Point = Point; })(Shapes = App.Shapes || (App.Shapes = {}));})(App || (App = {}));/// <reference path="./other.ts" />var App;(function (App) { var point = new App.Shapes.Point(); console.log(point.x);})(App || (App = {}));

Reference path + --outFile flag when compilingExternal file

App file

Concatenated

For IDE support

Modules, amd, systemjs--module flag and compile for either amd or system2

tsc app.ts –out –module amd|system

Choose one of these two

// app.tsimport x = require('./other');

var point = new x.Point();console.log(point.x);

// other.tsexport class Point { constructor( public x= 0, public y= 0 ) { }}

External file

App file

No namespace this time,BUT we use an import instead

Result is concatenated file

BUT we need to serve it using either requirejsor system.js for it to work

1)

2)

Modules, commonjs , es2015Typescript compiler doesn’t support commonjs yet.. So we need to use browserify to crawlour dependencies. But becaue we need to compile typescript while doing so we need tsify, whichis a version of the typescript compiler that works with browserify

export class Point {

constructor(public x= 0, public y= 0) { }

}

export class Rectangle { constructor( public x: number, public y: number, public width: number, public height: number ) {

}

getArea() { return this.width * this.height; }}

import { Point, Rectangle } from './other';

var point = new Point();

var rect = new Rectangle(1, 2, 3, 4);

browserify app.ts -p tsify --debug > bundle.js

Then ONLY way you want to work with typescript and modules,this is how angular 2 team uses modules

External file App file

Sourcemaps

Turning ng1 es5 into ng 1 typescript + es6 modules

Controllers

Services

Models

Bootstrapping

What needs changing?

ControllersCtrl.$inject = ['$scope','service'];

function Controller($scope, service){ $scope.prop = service.getValue();}

angular.module('app').controller('ctrl', Ctrl);

export class Controller { prop:string; static $inject = ['service']; constructor(private service){ this.prop = service.getValue(); }}

- From constructor function to class

- $scope disappears, $scope properties = class fields

- $inject becomes static field

ServicesService.$inject = ['$http'];

function Service { return { getData : getData } function getData(){ return $http.get('url'); }}

angular.module('app').factory('service', Service);

export interface IService { getData();}

export class Service { static $inject = ['$http'];

constructor(private $http) { } getData() { return this.$http.get('url'); }}

- From constructor function to class

- function becomes method with NO function keyword

- $inject becomes static field- we add an interface to be clear about what class does ( optional )

Modelfunction ModelFactory(){ function Model(dto){ this.prop = dto.prop; } return Model;}

angular.module('app').factory('Model', ModelFactory);

export class Model { private prop:string; constructor(dto) { this.prop = dto.prop }}

- ModelFactory removed

- Model becomes a class- everything on this becomes a field- no dependencies so NO MORE ANGULAR

Value & constAll these become vanilla javascript

const baseUrl =‘http://www.mydomain.com';

// etc..

export { baseUrl, someOtherConst..}

import { baseUrl, someOtherConst }

class Service{ doStuff(){ http.get( baseUrl ); }}

Bootstrappingimport { Service } from './service'import { Controller } from './controller'

Service.$inject = ['$http'];Controller.$inject = ['service'];

angular .module('app',[]) .controller('ctrl', Ctrl) .service('service', Service);

One file to wire up application ( could be split insmaller files )

import all definitions

wire up application

Bootstrapping- compilingbrowserify app.ts -p tsify --debug > bundle.js

Browserify to crawl our dependencies

Tsify, wrapped typescript compiler that togetherwith browserify understands ES6 modules

Thank you