Using E6 with Typescript

April 29, 2016

  • Syntax
  • Built-ins
  • Engine

To use the new features today we can use a Transpiler.

sudo npm install -g tsc

ES6 Syntax

ES6-style TypeScript -> Typescript as transpiler -> ES5 Javascript
ES6-style Typescript -> Typescript -> ES6 Javascript -> Babel -> ES5 Javascript

Characteristics of var

  • Hoisting
  • Functional scope

Characteristics of let

  • Not hoisted
  • Block-scoped

Characteristics of const

  • Not hoisted
  • Block-scoped
  • A value MUST be set on const declaration
  • Can’t be changed later

  • Can’t avoid properties from objects to change*
  • Can’t declare class members with const in ES6
  • Works with modules
  • Can’t declare class members with const in ES6

We can accomplish this with namespace, but it is not standard in ES6. Only in Typescript.

namespace AtomicNumbers {
    export const H = 1;
    export const He = 2;
}

ES6 Arrow Functions

  • shorthand sytnax for functions
  • simplifies the behavior of this
    • Value of this is always the containg code
    • “Lexical Binding”
    • Nested arrow functions share the same this
  • No built-in arguments object
    • We need to use an ES6 “rest” parameter instead
  • Arrow functions aren’t new-able
function greet(name) {
    return 'Hello, ' + name;
}
const greet = (name: string) => "Hello, " + name;

Destructuring

Break-up an object or array into component variables

interface USPostalAddress {
    streetAddress1: string;
    streetAddress2?: string; // ? means optional
    city: string;
    state: string;
    zip: string;
    country: string;
}
const addressData1 = {
    streetAddress1: '1001 Main Street',
    streetAddress2: '3rd Floor',
    city: 'Anytown',
    state: 'NY',
    zip: '10001-1234',
    country: 'USA'
};
// the default value in streetAddress2: stret2 = "" only applies if it is undefined
const {streetAdress1: street1, streetAddress2: street2 = "", city, state, zip, country} = addressData1;

Destructuring Arrays with Rest and Spread

const names = ['Alice', 'Bob', 'Charlie','Dana','Elvis','Fran','George'];
const firstTraditional = names[0];
// the first element in the array will be assigned to firstDestructure, the second to secondDestructure
const [firstDestructure, secondDestructure] = names;

// if names was empty, all the variables would get undefined
// you can set default values
const [firstDestructure = 'Steve', secondDestructure] = names || [];
// we can also get the rest of the elements not assigned in another variable
const [firstDestructure = 'Steve', secondDestructure, ...more] = names || [];
// thanks to this ... notation, it will give us an empty array if we call it with no parameters
// in this example we have a list of strings and we get an array of strings
multiGreet('Alice','Bob', 'Charlie');

function multiGreet(...items) {
    items.forEach(
        (item) => {
            console.log('Hello, '+item)
        };
    );
}
// if we have an array and we want to pass it as a list of arguments we can do it like this:

multiGreet(...names);
const names = ['Alice','Bob','Charlie','Dana'];
const names2 = ['Isaac','Jane'];

// merge arrays and add another element, which makes sense with what we've said before about getting a list of elements from an array
const names3 = [...names,...names2, Kyle];

ES6 String Templates

const myCar = 'BMW M3';

const useBackTick = `Hello World!`;

const substitutions = `I love ${myCar}!`;

console.log(`Hello, ${item}.`);

Tagged String Templates

function multiGreet(...items: string[]) {
    items.forEach(item => {
        console.log(friend`Hello, ${item}.`);
        });
}

function friend(strings: string[], ...substitutions: string[]) {
    if (!substitutions[0]) {
        substitutions[0] = 'Friend';
    }
    return processTaggedTemplate(strings, substitutions);
}

function processTaggedTemplate(strings: string[], substitutions: string[]) {
    const result = [];
    substitutions.forEach((sub,index) => {
        result.push(strings[index],sub);
        });
    result.push(strings[strings.length -1]);
    return result.join('');
}

Using the ES6 for of Loop

const names = ['Alice','Bob','Charlie','Dana','Elvis','Fran','George','Hope'];

names.forEach(item => {console.log(item);}); // values

for(let item in names){ // indexes
    console.log(names[item]);
}

for (let item of names) { // values
    console.log(item);
}

ES6 Modules

Exporting and importing objects:

  • Require.js
  • SystemJS
  • node.js

WHATWG: Web Hypertext Application Technology Working Group

https://whatwg.github.io/loader/

Typescript can Transpile to:

  • CommonJS
  • AMD
  • UMD
  • System

Introduction

With the introduction of ES6 Modules we don’t polute the global scope and therefore we have to explicitly export and import the functionality we want to use.

// library.js

function doSomething() {
    
}

export dosomething;
// program.js
import {doSomething} from "library";

doSomething();

Converting a File to an ES6 Module

export function helloWorld() {
    console.log('Hello World');
}
function helloWorld() {
    console.log('Hello World');
}

export {helloWorld}
export {helloWorld, someFunction, someVariable, someClass};
export {wowify as superWowify}

Importing an ES6 Module

import * as hello from './helloWorld';

hello.hello();
import {hello} from './helloWorld';

hello();
import {hello as h} from './helloWorld';

h();
import {hello, goodbye} from './helloWorld';

hello();
goodbye();

Default Exports

export {wowify as default, mehify};
import {default as wowify} from './wowify';
import wowify, {mehify} from './wowify';

AMD and RequireJS (broweser side)

AMD: Asynchronous Module Definition

// tsconfg.json
{
    "version": "1.5.0-beta",
    "compilerOptions": {
        "target": "es5",
        "module": "amd",
        // ...
    }
}
<script data-main="program" src="/scripts/require/require.js"></script>

Ambient External Module Declarations

Using CommonJS and Node

// tsconfg.json
{
    "version": "1.5.0-beta",
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        // ...
    }
}

Using the UMD Module Format

// tsconfg.json
{
    "version": "1.5.0-beta",
    "compilerOptions": {
        "target": "es5",
        "module": "umd",
        // ...
    }
}

SystemJS

  • System.register()
  • Polyfill for new System object
  • AMD
  • CommonJS
  • Shim for “Global” JS
  • Non-JS, CSS, JSON images
// tsconfg.json
{
    "version": "1.5.0-beta",
    "compilerOptions": {
        "target": "es5",
        "module": "system",
        // ...
    }
}
<script src="/scripts/systemjs/sytem.js"></script>
<script src="../system-config.js"></script>

ES6 Classes

  • constructor functions
  • instance properties and methods
  • static properties and methods
  • inheritance

Programming with Objects in ES6

  • Before ES6
    • Prototypal Inheritance or Delegation
    • Simple Object Literals
    • Factory Functions
  • Classes

Creating and Using an ES6 Class

class Contact {
    
}

const alice = new Contact();
alice.name = 'Alice';
alice.phone = '555-1212';
alice.email = '[email protected]';

console.log(JSON.stringify(alice));
class Contact {
    name: string;
    phone: string;
    email: string;
}

const alice = new Contact();
alice.name = 'Alice';
alice.phone = '555-1212';
alice.email = '[email protected]';

console.log(JSON.stringify(alice));

Using a Constructor

class Contact {
    name: string;
    phone: string;
    email: string;
    constructor(name, phone, email?) {
        // the question mark it's from typescript and makes it optional
        this.name = name:
        this.phone = phone;
        this.email = email;
    }

    const alice = new Contact('Alice','555-1212','[email protected]');
}

Destructuring in a Constructor Signature

    const alice2 = new Contact({name: 'Alice', phone: '555-1212'});

Methods

  • public (by default)
  • protected
  • private (but not at runtime)
class Contact {
    email: string;
    phone: string;
    name: string;
    greet(greetee: string) {
        return `Hello, ${greetee}, my name is ${this.name}`;
    }

    constructor({name, phone, email = undefined}) {
        this.name = name;
        this.phone = phone;
        this.email = email;
    }
}

Inheritance

Class declerations are not hoisted

class Employee extends Contact {
    employeeId: string;
    hireDate: Date;
}

const pat = new Employee({name: 'Pat', phone: '555-1213'});
pat.hireDate = new Date('2015-01-01');
console.log(pat.hireDate.toUTCString());
console.log(pat.greet('Renee'));
console.log(pat instanceof Employee); // true
console.log(pat instanceof Contact); // true

Calling Code from the Derived Class

Try to avoid deep class hierarchies

Compose complex classes by implementing multiple interfaces

class Employee extends Contact {
    employeeID: string;
    hireDate: Date;
    constructor({name, phone, email = undefined, employeeID, hireDate}) {
        super({name, phone, email});
        this.employeeI = employeeID;
        this.hireDate = hireDate;
    }
    greet(greetee: string) {
        return super.greet(greetee) + ' By the way, I'm an employee!;
    }
}

Accessors

class Employee extends Contact {
    private _employeeID: string;
    get employeeId() {
        return this._employeeID;
    }
    set employeeID(value) {
        // if we set a value in the constructor, it will call this setter
        this._employeeID = (value || "").toLocaleUpperCase();
    }
}

Class Expressions

const useFakes = true;

if (!useFakes){
    var MyWebService = class MyWebService {
        getData(id) {
            // expensive call.
        }
    }
} else {
    var MyWebService = class Fake_MyWebService {
        getData(id) {
            console.log(`Just logging: ${id}`);
        }
    }
}

var webService = new MyWebService();
webService.getData(5);
const useFakes = true;

if (!useFakes){
    var MyWebService = new (class MyWebService {
        getData(id) {
            // expensive call.
        }
    })();
} else {
    var MyWebService = new (class Fake_MyWebService {
        getData(id) {
            console.log(`Just logging: ${id}`);
        }
    })();
}

var webService = MyWebService;
webService.getData(5);

Static Methods

  • Utility functions
  • Caching
  • Tracking class metadata
class TestStatic {
    static doubleNumber(num) {
        return num * 2;
    }
    static count = 0; // only works with Typescript not ES6
    constructor() {
        TestStatic.count += 1;
    }
}

console.log(TestStatic.doubleNumber(10));
var ts1 = new TestStatic();
var ts2 = new TestStatic();
console.log(TestStatic.count);

Abstract Classes and Interfaces

interface ISprite {
    x: number;
    y: number;
    imageUrl: string;
    update: () => void;
}

abstract class Sprite implements ISprite {
    x: number;
    y: number;
    imageUrl: string;
    abstract update();
}

class Player extends Sprite {
    update() {
        // do whatever
    }   
}

class Monster extends Sprite {
    update() {
        // do whatever
    }
}