Introduction to ECMAScript 6

Contents

  • What is ECMAScript?
  • Hands-on - transpailing, demo

ES6 ~ ES2015

  • Classes, Inheritance
  • Modules
  • Iterators, Generators
  • Scoping
  • Promises, Proxies
  • Arrow functions
  • Collections
  • ...

TypeScirpt

  • Type Inference
  • Type compatibility
  • Optional parameters 
  • Enums
  • Interfaces
  • Classes
  • Mixing, Mergins
  • Decleration files
  • ...

What is ECMAScript?

  • A standard for scripting languages
  • JavaScript most popular dialect
  • Also: ActionScript, Lua

May '95

Dec '95

Sep '95

1997

Aug '96

1999

1998

2009

2003

2015

2011

2016

Mocha (Brendan Eich, Netscape)

LiveScript

JavaScript

Edition 1

JScript (Microsoft)

Edition 2

Edition 3

Edition 4

Edition 5

Edition 5.1

Edition 6

Edition 7

ECMA-262 specification

What is ECMAScript?

  • Latest finalised version ECMAScript6 (ES6, ES2015, Harmony)
  • Change to yearly naming convention (ES7 = ES2016)

Compatibility

  • Native support in browsers still being implemented
  • Support in browsers through transpiling

ES6 Transpilation

  • Transpiler  is a type of compiler that takes the source code of a program written in one programming language as its input and produces the equivalent source code in another programming language. 
  • ES5 widely supported by browsers
  • Two main ES6 -> ES5 transpilers
  • ES6 should be compiled to ES5 for compatibility
  • Traceur
  • Babel

ES6 Transpilation

  • Babel generally preferred:
    • More readable transpiled code
    • JSX support (compatibility with React.js)
  • Alternatively use higher-level language implementing ES6 features
    • CoffeeScript
    • Dart
    • Typescript (more later)
  • Babel ES6 -> ES5 transpilation
/*jshint esnext: true */

import Todo from './todo';
import {values} from './generators';

class TodoList {
  constructor() {
    this.todos = {}; 
    this.length = 0;
  }

  add(text, done = false) {
    let todo = new Todo(text, done);
    this.todos[String(todo.timestamp)] = todo;
    this.length++;
  }

  archiveCompleted() {
    for (let todo of values(this.todos)) {
      if (todo.done) this.delete(todo);
    }
  }

  delete(todo) {
    delete this.todos[String(todo.timestamp)];
    this.length--;
  }

  getUncompletedCount() {
    let count = 0;
    for (let todo of values(this.todos)) {
      if (!todo.done) count++;
    }
    return count;
  }
}

export default TodoList;
/*jshint esnext: true */

'use strict';

Object.defineProperty(exports, '__esModule', {
  value: true
});

var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }

var _todo = require('./todo');

var _todo2 = _interopRequireDefault(_todo);

var _generators = require('./generators');

var TodoList = (function () {
  function TodoList() {
    _classCallCheck(this, TodoList);

    this.todos = {};
    this.length = 0;
  }

  _createClass(TodoList, [{
    key: 'add',
    value: function add(text) {
      var done = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];

      var todo = new _todo2['default'](text, done);
      this.todos[String(todo.timestamp)] = todo;
      this.length++;
    }
  }, {
    key: 'archiveCompleted',
    value: function archiveCompleted() {
      var _iteratorNormalCompletion = true;
      var _didIteratorError = false;
      var _iteratorError = undefined;

      try {
        for (var _iterator = (0, _generators.values)(this.todos)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
          var todo = _step.value;

          if (todo.done) this['delete'](todo);
        }
      } catch (err) {
        _didIteratorError = true;
        _iteratorError = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion && _iterator['return']) {
            _iterator['return']();
          }
        } finally {
          if (_didIteratorError) {
            throw _iteratorError;
          }
        }
      }
    }
  }, {
    key: 'delete',
    value: function _delete(todo) {
      delete this.todos[String(todo.timestamp)];
      this.length--;
    }
  }, {
    key: 'getUncompletedCount',
    value: function getUncompletedCount() {
      var count = 0;
      var _iteratorNormalCompletion2 = true;
      var _didIteratorError2 = false;
      var _iteratorError2 = undefined;

      try {
        for (var _iterator2 = (0, _generators.values)(this.todos)[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
          var todo = _step2.value;

          if (!todo.done) count++;
        }
      } catch (err) {
        _didIteratorError2 = true;
        _iteratorError2 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion2 && _iterator2['return']) {
            _iterator2['return']();
          }
        } finally {
          if (_didIteratorError2) {
            throw _iteratorError2;
          }
        }
      }

      return count;
    }
  }]);

  return TodoList;
})();

exports['default'] = TodoList;
module.exports = exports['default'];

ES5

ES6

  • Traceur ES6 -> ES5 transpilation
/*jshint esnext: true */

import Todo from './todo';
import {values} from './generators';

class TodoList {
  constructor() {
    this.todos = {}; 
    this.length = 0;
  }

  add(text, done = false) {
    let todo = new Todo(text, done);
    this.todos[String(todo.timestamp)] = todo;
    this.length++;
  }

  archiveCompleted() {
    for (let todo of values(this.todos)) {
      if (todo.done) this.delete(todo);
    }
  }

  delete(todo) {
    delete this.todos[String(todo.timestamp)];
    this.length--;
  }

  getUncompletedCount() {
    let count = 0;
    for (let todo of values(this.todos)) {
      if (!todo.done) count++;
    }
    return count;
  }
}

export default TodoList;
$traceurRuntime.registerModule("todoservice.js", [], function() {
  "use strict";
  var __moduleName = "todoservice.js";
  var Todo = $traceurRuntime.getModule($traceurRuntime.normalizeModuleName("./todo", "todoservice.js")).default;
  var values = $traceurRuntime.getModule($traceurRuntime.normalizeModuleName("./generators", "todoservice.js")).values;
  var TodoList = function() {
    function TodoList() {
      this.todos = {};
      this.length = 0;
    }
    return ($traceurRuntime.createClass)(TodoList, {
      add: function(text) {
        var done = arguments[1] !== (void 0) ? arguments[1] : false;
        var todo = new Todo(text, done);
        this.todos[String(todo.timestamp)] = todo;
        this.length++;
      },
      archiveCompleted: function() {
        var $__7 = true;
        var $__8 = false;
        var $__9 = undefined;
        try {
          for (var $__5 = void 0,
              $__4 = (values(this.todos))[Symbol.iterator](); !($__7 = ($__5 = $__4.next()).done); $__7 = true) {
            var todo = $__5.value;
            {
              if (todo.done)
                this.delete(todo);
            }
          }
        } catch ($__10) {
          $__8 = true;
          $__9 = $__10;
        } finally {
          try {
            if (!$__7 && $__4.return != null) {
              $__4.return();
            }
          } finally {
            if ($__8) {
              throw $__9;
            }
          }
        }
      },
      delete: function(todo) {
        delete this.todos[String(todo.timestamp)];
        this.length--;
      },
      getUncompletedCount: function() {
        var count = 0;
        var $__7 = true;
        var $__8 = false;
        var $__9 = undefined;
        try {
          for (var $__5 = void 0,
              $__4 = (values(this.todos))[Symbol.iterator](); !($__7 = ($__5 = $__4.next()).done); $__7 = true) {
            var todo = $__5.value;
            {
              if (!todo.done)
                count++;
            }
          }
        } catch ($__10) {
          $__8 = true;
          $__9 = $__10;
        } finally {
          try {
            if (!$__7 && $__4.return != null) {
              $__4.return();
            }
          } finally {
            if ($__8) {
              throw $__9;
            }
          }
        }
        return count;
      }
    }, {});
  }();
  var $__default = TodoList;
  return {get default() {
      return $__default;
    }};
});
$traceurRuntime.getModule("todoservice.js" + '');

ES5

ES6

  • TypeScript -> ES5 transpilation
// Todo Model
// ----------
// Our basic **Todo** model has `content`, `order`, and `done` attributes.
class Todo extends Backbone.Model {

    // Default attributes for the todo.
    defaults() {
        return {
            content: "empty todo...",
            done: false
        }
    }

        // Ensure that each todo created has `content`.
    initialize() {
        if (!this.get("content")) {
            this.set({ "content": this.defaults().content });
        }
    }

    // Toggle the `done` state of this todo item.
    toggle() {
        this.save({ done: !this.get("done") });
    }

    // Remove this Todo from *localStorage* and delete its view.
    clear() {
        this.destroy();
    }

}
// Todo Model
// ----------
// Our basic **Todo** model has `content`, `order`, and `done` attributes.
var Todo = (function (_super) {
    __extends(Todo, _super);
    function Todo() {
        _super.apply(this, arguments);
    }
    // Default attributes for the todo.
    Todo.prototype.defaults = function () {
        return {
            content: "empty todo...",
            done: false
        };
    };
    // Ensure that each todo created has `content`.
    Todo.prototype.initialize = function () {
        if (!this.get("content")) {
            this.set({ "content": this.defaults().content });
        }
    };
    // Toggle the `done` state of this todo item.
    Todo.prototype.toggle = function () {
        this.save({ done: !this.get("done") });
    };
    // Remove this Todo from *localStorage* and delete its view.
    Todo.prototype.clear = function () {
        this.destroy();
    };
    return Todo;
}(Backbone.Model));

ES5

ES6

New features in ES6

Classes

  • ES5 objects inherit from Object.protoype 
  • Can instantiate and define methods using the protoype:
function Client (firstname, lastname) {
  this.id = undefined
  this.firstname = firstname
  this.lastname = lastname
}
Client.prototype.getFullName = function () {
    return this.firstname + this.lastname;
}

Client.prototype.setId = function () {
    this.id = generateGuid();
}
var Client = new Client()
client.getFullName()
client.setId()

ES5

ES5

ES5

this

var foo = {};

foo.someMethod = function(){
    alert(this);
}
 var foo = function(){
    alert(this);
 }
 foo();
foo.someOtherMethod = function (){
    var that=this;
    function bar(){
        alert(that);
    }
}
function Foo(){
    this.confusing = 'hell yeah';
}
var myObject = new Foo();
var foo = {};
foo.someMethod = function(){ console.log(this.toString()); }

foo.someMethod()                                    // [object Object]
foo.someMethod.apply (foo, [])          // [object Object]
foo.someMethod.apply ("foo", [])       //  foo

Classes

  • ES6 classes are 'syntactic sugar' on top of prototypal inheritance
  • Makes classes easier to read/express
class Client {

    constructor(firstname, lastname) {
        this.id = undefined
        this.firstname = firstname
        this.lastname = lastname
    }

    getFullName() {
        return this.firstname + this.lastname
    }

    generateId() {
        this.id = generateGuid()
    }

}
function Client (firstname, lastname) {
  this.id = undefined
  this.firstname = firstname
  this.lastname = lastname
}

Client.prototype.getFullName = function () {
    return this.firstname + this.lastname
}

Client.prototype.setId = function () {
    this.id = generateGuid()
}

ES6

ES5

Classes

class Client {

    constructor(firstname, lastname) {
        this.id = undefined
        this.firstname = firstname
        this.lastname = lastname
    }

    getFullName() {
        return this.firstname + this.lastname
    }

    generateId() {
        this.id = generateGuid()
    }

}
  • Method signature notation - shorter, cleaner
  • No commas between properties, methods

ES6

Classes - static methods

class Client {

    constructor(firstname, lastname) {
        this.id = undefined
        this.firstname = firstname
        this.lastname = lastname
    }

    getFullName() {
        return this.firstname + this.lastname
    }

    generateId() {
        this.id = generateGuid()
    }

    static generateGuid() {
        function s4() {
            return Math.floor((1 + Math.random()) * 0x10000)
            .toString(16)
            .substring(1)
        }
        
        return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
        s4() + '-' + s4() + s4() + s4()
    }
    
}
  • Can't be called on an instantiated class
  • No access to 'this' properties/methods
  • Most useful when:
  • Associated with class
  • Don't need unique variables of instanced class

ES6

New features in ES6

Inheritance

  • In ES5, the Object.create(parentObject) method creates object-to-object inheritance
function Client (firstname, lastname) {
    this.id = undefined
    this.firstname = firstname
    this.lastname = lastname
}
var TCSClient = Object.create(Client)

TCSClient.evalSystem = "TCS"

TCSClient.firstname = "Joe Bloggs"
  • Can add extra properties to child object
  • If property does not exist, checks further up prototype 'chain'

ES5

ES5

Inheritance (extend)

  • ES6 introduces the 'extend' keyword
class TCSClient extends Client {

    this.evalSystem = "TCS"

}
var TCSClient = Object.create(Client);

TCSClient.evalSystem = "TCS"
  • Can override functions by re-implementing them in child class

ES5

ES6

class TCSClient extends Client {

    getFullName() {
        return this.firstname + " " +
            this.lastname
    }

}
var TCSClient = Object.create(Client)

TCSClient.prototype.getFullName() {
    return this.firstname + " " +
        this.lastname
}

ES5

ES6

  • Can access methods of the parent object using super()
class DRClient extends Client {

    this.vcNumberPrefix = "W"

    generateVCNumber() {
        super.generateVCNumber()
    }

}
var TCSClient = Object.create(Client)

TCSClient.vcNumberPrefix = "A"

TCSClient.prototype.generateVCNumber = function() {
    Client.prototype.generateVCNumber.call(this)
}

ES5

ES6

Inheritance (super)

function Client () {
    this.vcNumber = undefined
    this.vcNumberPrefix = ""
}

Client.prototype.generateVCNumber = function () {
    var number = Math.floor(Math.random() * 10000000)
    this.vcNumber = this.vcNumberPrefix + pad(number, 7)
}

ES5

  • Parent class (ES5)
  • .call(this) calls function of same name on parent object
  • use super keyword to use base function, current object implied

New features in ES6

Modules

  • ES6 brings formal standardization
    • ​Currently: AMD, CommonJS
    • Before that: None (global)
  • In ECMAScript 6, modules are stored in files.
    • one module per file
    •  one file per module

Modules (current)

  • CommonJS
  • AMD
// someModule.js
function doSomething () {
    console.log("foo");
}

exports.doSomething = doSomething;


//otherModule.js
var someModule = require('someModule');    
someModule.doSomething();
// someModule.js
define(["lib"], function (lib) {
    function doSomething() {
           console.log("foo");
    }
    return {
        doSomething: doSomething
    }
});

//otherModule.js
require(["someModule"], function(someModule){
    someModule.doSomething();
});
  • Compact syntax
  • Synchronous loading
  • Server side (Node.js)
  • Asynchronous loading
  • No compilation step
  • Browser (RequireJS)

Modules (ES6)

  • Similar to CommonJS
    • compact syntax
    • preference for single exports 
    • support for cyclic dependencies
  • Similar to AMD
    • direct support for asynchronous loading
    • configurable module loading

Modules

  • Exported bindings are available to other modules that import them
// ClientService.js

class ClientService {
    
    clients = [];

    addClient(client) {
        this.clients.add(client);
    }

}

export default ClientService;
import ClientService from './ClientService';

class ClientComponent {

    submitClient(client) {

        ClientService.addClient(client);

    }

}

ES6

ES6

Modules

  • Bindings can be exported as:

Default exports

//------ MyClass.js ------
class MyClass { ... };
export default MyClass;

//------MyFunction.js ----
function myFunction() { ... };
export default myFunction;
//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
    return x * x;
}
export function diag(x, y) {
    return sqrt(square(x) + square(y));
}

Named exports

// ----- main.js ---
import * as lib from './lib';
console.log(lib.square(11)); // 121
console.log(lib.diag(4, 3)); // 5
// ----- main.js ---
import MyClass from './MyClass'
import thatFunction from './myFunction'

var my = new MyClass();
var res = thatFunction();

Modules

  • Default exports
    • Only one per module
    • Can rename on import
// ClientService.js

class ClientService {
    
    clients = [];

    addClient(client) {
        this.clients.add(client);
    }

}

export default ClientService;

ES6

import ClientService from './ClientService';

ES6

import MyService from './ClientService';

class ClientComponent {

    submitClient(client) {

        MyService.addClient(client);

    }
}
  • Difference in CommonJS / ES6 syntax
module.exports = ClientService;

CommonJS

export default ClientService;

ES6

var Service = require('./ClientService');
import Service from ('./ClientService');
  • CommonJS and ES6 modules are interoperable!

Modules (ES6 vs CommonJS)

function foo() {
    module.exports = 'bar';    // OK
}

CommonJS

Modules (ES6 vs CommonJS)

  • Exports in ES6 must be top-level
function foo() {
    export default 'bar';    // Syntax error
}

ES6

// Constants.js

var $BANKRUPTCY_MIN_DEBT = 1001;
var $SEQUESTRATION_MIN_DEBT = 3000;

export $BANKRUPTCY_MIN_DEBT;
export $SEQUESTRATION_MIN_DEBT;

ES6

Modules (named exports)

// Constants.js

var $BANKRUPTCY_MIN_DEBT = 1001;
var $SEQUESTRATION_MIN_DEBT = 3000;

export { $BANKRUPTCY_MIN_DEBT,
    $SEQUESTRATION_MIN_DEBT}

ES6

  • Can be expressed as a list
  • many exports per module
  • can mix with default export
  • Importing named exports:

Modules (named exports)

ES6

ES6

import {$BANKRUPTCY_MIN_DEBT,
    $SEQUESTRATION_MIN_DEBT} from './Constants';

function isEligibleForBankruptcySequestration(
    isScottish, debtTotal) {
   
    var minDebt;

    if(isScottish) { minDebt = $SEQUESTRATION_MIN_DEBT }
        else { minDebt = $BANKRUPTCY_MIN_DEBT };

    return debtTotal >= minDebt;

}
// Constants.js

var $BANKRUPTCY_MIN_DEBT = 1001;
var $SEQUESTRATION_MIN_DEBT = 3000;

export $BANKRUPTCY_MIN_DEBT;
export $SEQUESTRATION_MIN_DEBT;
  • Can rename when importing:

Modules

ES6

ES6

import {$BANKRUPTCY_MIN_DEBT as minBank,
    $SEQUESTRATION_MIN_DEBT as minSeq
    } from './Constants';

function isEligibleForBankruptcySequestration(
    isScottish, debtTotal) {
   
    var minDebt;

    if(isScottish) { minDebt = minSeq }
        else { minDebt = minBank };

    return debtTotal >= minDebt;

}
// Constants.js

var $BANKRUPTCY_MIN_DEBT = 1001;
var $SEQUESTRATION_MIN_DEBT = 3000;

export $BANKRUPTCY_MIN_DEBT;
export $SEQUESTRATION_MIN_DEBT;
  • Importing the entire namespace:

Modules

ES6

ES6

import * as incomes from './IncomesController'

function submitIncomes() {

    var valIncomeSupport = incomes.incomeSupport;
    var valJobseekers = incomes.jobseekers ;
    var valIncapacity = incomes.incapacity ;
    
    var valTotalBenefitIncome = incomes.totalBenefitIncome();

}
// IncomesController.js

export var incomeSupport;
export var jobSeekers;
export var incapacity;

export var totalBenefitIncome = function() {
    incomeSupport +
        jobSeekers +
        incapacity;
}
  • Any default export would be placed in alias.default
  • A namespace import must have an alias

Modules

  • Modules export bindings - not values or references
  • Imported items subject to change:
//------ lib.js ------
export let mutableValue = 3;
export function incMutableValue() {
    mutableValue++;
}
//------ main.js ------
import * as lib from './lib';

// The imported value is live
console.log(lib.mutableValue); // 3
lib.incMutableValue();
console.log(lib.mutableValue); // 4

// The imported value can’t be changed
lib.mutableValue++; // TypeError

ES6

ES6

  • Why bindings? Easier to deal with cyclic dependencies

Modules

  • No native support in current browsers
  • Transpilers concatenate modules into one file
  • 'Strict mode' on by default in ES6 modules:
  • Variables can't be left undeclared
  • Function parameters need unique names
  • Can't use 'with'
  • Can't use octal numbers
  • Errors thrown on assignment to read only properties
  • Reserved words can't be bound (e.g. protected, static, interface)

New features in ES6

Symbols

  • New primitive type
  • Similar to strings:
  • Immutable
  • Can't add properties on them (as with any primitive)
  • Can be used as a property key
var mySymbol = Symbol("description");

console.log(typeof mySymbol);    // 'symbol'

ES6

Symbols

  • Unlike strings:
  • Unique (even where description is same)
Symbol('foo') === Symbol('foo');    // false

ES6

  • Does not use 'new' operator:
var symbol = new Symbol();    // TypeError

ES6

Symbols

  • Use cases:
  • Avoiding name clashes in property keys - e.g.
  • May want to set a flag on a moving element:
if (element.isMoving) {
  smoothAnimations(element);
}
element.isMoving = true;
  • Potential for clashes with existing / future libraries
var isMoving = Symbol("isMoving");

...

if (element[isMoving]) {
  smoothAnimations(element);
}
element[isMoving] = true;

ES6

  • Creates a unique property in scope - no collisions

Symbols

  • Use cases:
  • Defining protocols
  • Can flag an object as having a set of common methods by attaching certain symbols, e.g.
  • Iterable objects use the 'well-known' Symbol.iterator
var myArray = ['a', 'b', 'c'];

var myIterable = myArray[Symbol.iterator]();
> iterable.next()
{ value: 'a', done: false };
> iterable.next()
{ value: 'b', done: false };
> iterable.next()
{ value: 'c', done: false };
> iterable.next()
{ value: undefined, done: true };
  • myArray has Symbol.iterator property:
  • each item in collection has 'value' and 'done' properties
  • can iterate through list with next()

Symbols

  • Can place symbols on the 'global registry'
  • Accessible across code realms
Symbol.for('foo') === Symbol.for('foo');   // true
  • Symbol.for(key)
  • Returns symbol with key from global registry
  • Creates a symbol with key if doesn't exist
  • Symbol.keyFor(symbol)
  • Returns symbol with key from global registry
var mySymbol = Symbol.for('foo');

console.log(Symbol.keyFor(mySymbol));    // 'foo'

New features in ES6

Iterators

  • ES6 introduces iterator and iterable protocols
  • 'Iterability' involves data sources and data consumers

Iterators

  • Data consumers:
  • for - of loops
  • Array.from
  • spread operator (more later)
  • Data sources: some built-ins are iterable by default in ES6
  • Arrays
  • Strings
  • Maps
  • Sets
  • Arguments (of a function)
  • DOM data structures (e.g. querySelectorAll method)

Iterators

  • The Symbol.iterator is used to assign an 'iterator' to any object
  • Symbol.iterator equates to a method that must return an object that complies with the iterator protocol.
var iterable = { 
    next: function(){ 
        returns {value: 1, done: true}
    }
}
var foo = { [Symbol.iterator]: iterable }        
foo[Symbol.iterator] = iterable

iterable.next() // returns {value: 1, done: true}

Iterators

  • Example:
[Symbol.iterator]: () => ({
    
    items: ['f', 'o', 'o'],

    next: function () {

        return {
            done: this.items.length === 0,
            value: this.items.shift()
        }
    }
})

ES6

next() method (no args)

returns Object with done, value

Adheres to iterator protocol

Iterators

  • All Objects have '@@iterator' method in ES6 spec
  • If an Object is given a valid [Symbol.iterator] method, this is assigned to @@iterator method
  • To recap:
  • Assign method to [Symbol.iterator]  to mark object as iterable
  • [Symbol.iterator] method must return object adhering to iterator protocol
  • This method becomes object's @@iterator method and this is called when object needs to be iterated

Iterators

  • Arrays, Maps, Objects all have their own default @@iterator methods
  • Method describes how to pull values from object using a data consumer, .e.g
  • for .. of loops (new to ES6)
  • Array.from
  • spread operator

New features in ES6

Generators

  • Special kind of iterator
  • 'Generator function' declared, using asterisk notation:
function* myGenerator() {}

// or

function *myGenerator() {}

ES6

Generators

  • Generator functions contain 'yield' expressions
  • On 'yield' expression, function:
function* myGenerator() {

    yield 'f';
    console.log('o');
    yield 'o';
    console.log('b');
    yield 'a';
    console.log('r');

}
  • Suspends execution
  • Emits a value
var fooBar = myGenerator();

for(var item of fooBar) {

    console.log(item);        // 'f' 'o' 'o' 'b' 'a' 'r'

}

ES6

ES6

Generators

  • Calling next() on Generator object starts function or resume from last yield expression
function *myGenerator(x) {
    
    var y = x + 3;
    var z = 1 + (yield(y));
    return z;
}

ES6

var gen = myGenerator(2);

console.log( gen.next() );

console.log( gen.next(6) );

ES6

1 - myGenerator function instantiated with param x = 2

{ value: 5, done: false }

1

2

2 - next() called on generator function. No 'yield' expression encountered yet, so no param

3

3 - execution paused on yield expression; var y passed out as 5

4 - console logs

4

7

5 - execution resumes with yield expression as passed in param (6)

6

6 - since yield expression is 6, z becomes 7 (1 + 6)

7 - end of generator function; console logs

{ value: 7, done: true }
  • Params passed to next() replace yield expression

5

New features in ES6

'let' and 'const'

  • Alternatives to declaring a variable with 'var'

 

  • 'var'
    • function-scoping
    • hoisting
  • 'let'
    • block-scoping
    • hositing w/ temporal dead zones

'let' and 'const'

  • Hoisting
  • variables and function declarations get pulled to the top of their scope
var foo = true;

function bar() {
	if (!foo) {
		var foo = "Foo is false!"
	};
	console.log(foo)
}

bar()

ES5

> Foo is false!

Console:

var foo;

function bar() {
    var foo;
    if (!foo) {
        foo = "Foo is false!"
    };
    console.log(foo);
}

foo = true;

bar();

ES5

var x = 1;
if (x) {
    var x = 2;
};
console.log(x);
> 2

Console:

ES5

'let' and 'const'

  • { } blocks do not create a new scope
  • What if we want a temporary scope for the inner x?
var x = 1;
if (x) {
    (function () {
        var x = 2;
    })
};
console.log(x);
> 1

Console:

ES5

'let' and 'const'

  • Could create a new function scope
var x = 1;
if (x) {
    let x = 2;
}
console.log(x);
> 1

Console:

ES6

'let' and 'const'

  • Using 'let' allows for block-scoping ( {} )
  • Maintains encapsulation principles
  • Allows same variable names where appropriate
if (x) {
  let foo;
  let foo; // TypeError thrown.
}
function do_something() {
  console.log(foo); // ReferenceError
  let foo = 2;
}

'let' and 'const'

  • Redeclaration not allowed
  • 'let' is also hoisted
    • Temporal dead-zone
for (let i = 0; i<10; i++) {
  console.log(i); // 0, 1, 2, ... 9
}

console.log(i); // i is not defined
  • 'let' in loops

'let' and 'const'

  • const:
  • must be decalred using an initializer
  • also block-scoped
const IP = 3.14
const STUFF = { things: ['chair', 'desk', 'computer'] };
const FOO; // SyntaxError: missing = in const declaration

e.g.

  • will fail silently if assigned again
const stuff = { things: ['chair', 'desk'] };
stuff = {};                                   // fails
console.log(stuff.things);                    // ...

e.g.

  • does not make the assigned value itself immutable
const stuff = { things: ['chair', 'desk', 'computer'] };
stuff.things.push('pot plant');
console.log(stuff.things);
// chair,desk,computer,pot plant

e.g.

New features in ES6

Arrow functions

  • Also known as lambda expressions
  • Makes code more 'terse'
    • Useful for anonymous functions with few arguments and statements
    •  '=>' replaces 'function'
function(param1, param2, ..., paramN) { statements }

// Equals to

(param1, param2, …, paramN) => { statements }

Arrow functions

  • No parentheses required for 1 argument
  • 'return' keyword implied for single expressions
function(a) { return a * a }

// Equals to 

a => a * a

  • () can be replaced with _
() => { statements }

// Equals to

_ => { statements }
(param1, param2, …, paramN) => { return expression; }

// Equals to

(param1, param2, …, paramN) => expression

Arrow functions (scope)

  • In ES5, functions create their own scope
  • Calling 'this' within the function refers to itself; not the scope outside the function
  • To bring outside scope into the function, store 'this' in a variable, or use .bind()
function Counter() {
    var that = this;
    that.seconds = 0;
    setInterval(function() {
        that.seconds++
    }, 1000);
}

ES5

function Counter() {
    this.seconds = 0;
    setInterval(function() {
        this.seconds++
    }.bind(this), 1000);
}

ES5

Arrow functions (scope)

  • Arrow functions are bound to their lexical scope
  • 'this' in an arrow function is the same as the outer scope
function Counter() {
    this.seconds = 0;
    setInterval(function() {
        this.seconds++
    }.bind(this), 1000);
}

ES5

function Counter() {
    this.seconds = 0;
    setInterval(() => this.seconds++, 1000);
}

ES6

New features in ES6

Collection types

  • New in ES6 : Map, Set, WeakMap, WeakSet
  • Previously only one collection type in JavaScript (Array)
  • Arrays only use numeric indices
  • Objects used when non-numeric indices necessary

Collection types

  • Map
  • ES5 maps often created using regular Objects
var hashMap = {};

function add(key, value) {
    hashMap[key] = value;
}

function get(key) {
    return hashMap[key];
}

add('request', { description: 'Simplified HTTP request client' });
add('moment', { description: 'Parse, validate, manipulate, and display dates' });
add('lodash', { description: 'The modern build of lodash modular utilities' });

Collection types

  • Map
  • Might want to use non-string values as keys
    • e.g. DOM elements
  • Problems with this approach:
    • Only allows strings as keys
    • Difficult to iterate through keys
var registry = {};

function add(element, meta) {
    registry[element] = meta;
}

function get(element) {
    return registry[element];
}

add(document.getElementById("wages-panel"), { display: true });
add(document.getElementById("child-income-panel"), { display: false });

Collection types

  • Map
add(document.getElementById("wages-panel"), { display: true });
  • <div> element is cast to string 
    • [Object HTMLDivElement]
    • Using objects as keys will mean constantly overwriting same key
  • Maps allow for any type to be used as key or value
var map = new Map();

map.set(new Date(), function today() {});
map.set(() => 'key', { foo: 'bar' });
map.set(Symbol('items'), [1, 2]);

Collection types

  • Map
  • Methods:
  • get(), set(), has(), delete()
  • entries(), keys(), values()
  • size(), clear()
  • forEach()
var items = [
    [new Date(), function today() {}],
    [() => 'key', { foo: 'bar' }],
    [(Symbol('items'), [1, 2])
]

// 2D key-value Array to map
var map = new Map(items);

map.get(Symbol('items')) // returns [1,2]
  • Relation with array

Collection types

  • WeakMap
  • A subset of Map, with some limitations:
  • Not iterable - no .entries(), .keys(), .values() or clear()
  • Can use .has(), .get(), .set(), .delete()
  • Every key must be an object - value types not admitted
  • A WeakMap holds references to its keys weakly
  • i.e. if no other references to a key, the entry will be garbage collected
  • WeakMap
  • Use case - Private class variables

Collection types

  • No concept of private class variables in ES6
const privateData = new WeakMap();

class MyClass {
    constructor (name, age) {
        privateData(this, { name: name, age: age });
    }

    getName () {
        return privateData.get(this).name;
    }

    getAge() {
        return privateData.get(this).age;
    }
}
  • When myClass destroyed no references to key (this) exists
  • privateData will be garbage collected
  • WeakMap
  • Storing data on a DOM element

Collection types

let answersMap = new WeakMap();

let childBenefit = document.getElementById('child-benefit');

myElement.addEventListener('change', function() {
    var answerFrequency = getFrequency();
    answersMap.set(childBenefit, {frequency: answerFrequency}
}, false);
  • Storing the frequency of an answer on the DOM element
let childNumber = document.getElementById('child-number')

if(childNumber.number === 0) {
    childBenefit.parentNode.removeChild(childBenefit)
}

console.log(answerMap.get(childBenefit)      //  undefined
  • If childBenefit key removed, entry is garbage collected
  • Set
  • Very similar to map, but only have values

Collection types

  • Values used as keys, so must be unique
  • set.set becomes set.add
  • no set.get() (get(value) => value)
  • WeakSet
  • use set when all values must be unique
  • As Map -> WeakMap
  • Not iterable
  • No other references to value - garbage collection

New features in ES6

Template literals

  • A string wrapped in backticks ( ` )
  • Can be multiline
var text = `
    This is a template literal
    'It can span multiple lines'
    "And can contain quotation marks"

    `

ES6

  • Strings can contain ' and " characters without escaping
var text = (
    'This is the old method\n' +
    'for strings spanning\n' +
    'several lines'
)

var text = [
    'Or',
    'perhaps',
    'this'
].join('\n')
    

ES5

Template literals

  • Expression interpolation with ${expression}
var text = `the time and date is ${today.toLocaleString()}`

// 'the time and date is 1/1/2016, 09:20:00 AM'

ES6

Template literals

  • Tagged template literals
    • modify the output of template literals using a function
    • must return string
var a = 5;
var b = 10;

function tag(strings, ...values) {
  console.log(strings[0]); // "Hello "
  console.log(strings[1]); // " world "
  console.log(values[0]);  // 15
  console.log(values[1]);  // 50

  return "Bazinga!";
}

tag`Hello ${ a + b } world ${ a * b }`;
// "Bazinga!"
  • Particularly useful for chunks for HTML with interpolated variables - e.g. Angular2 templates

Template literals

@View({

    template:`
        <div *ng-if="userService.isAnyUsers()">
    	    <div class="userlist-search">
	        <p><label>Search user:</label>  
                <input type="text" [(ng-model)]='searchTerm'></p>
    	    </div>
        </div>
    `

})

Angular2

New features in ES6

Object literals

  • ES6 enhancements:
  • Comma separated-list of name-value pairs in curly braces
var myObject = { foo:'bar', baz: 'qux' }

ES5

  • If property value matches property name, can omit value
var foo = { bar: 'bar' }

console.log(foo.bar)    // 'bar'

ES5

var bar = 'bar';
var foo = { bar };

console.log(foo.bar);    // 'bar'

ES6

Object literals

  • ES6 enhancements:
  • declaring methods on an object
var foo = {

    bar(baz) { }

}

ES6

var foo = {

    bar: function(baz) { }

}

ES5

  • 'function' keyword inferred from context

Object literals

  • ES6 enhancements:
  • Can compute property names using []
var bar = 'bar'
var baz = { [bar]: 'qux' }
console.log(baz)

// { bar: 'qux' }

ES6

var foo = { ['bar' + 'baz']: 'qux' }

console.log(foo)
// { barbaz: 'qux' }

ES6

New features in ES6

Assignment destructuring

  • An object literal lets us create multiple properties at the same time
let myObject = { foo:'bar', baz: 'qux' }
  • An object pattern lets us extract multiple properties at the same time

ES6

let { foo, baz } = myObject

console.log(foo)                        // 'bar'

console.log(baz)                        // 'qux'

ES6

  • Properties mapped to aliases

Assignment destructuring

  • Properties not found are saved as undefined
let myObject = { foo:'bar', baz: 'qux' }

let { norf: a } = myObject

console.log(a)                        // undefined

ES6

  • Properties can be mapped to aliases
let { foo : f, baz : b } = myObject

console.log(f)                        // 'bar'

console.log(b)                        // 'qux'

ES6

Assignment destructuring

  • Patterns can be nested arbitrarily deeply
let myObject = { foo: [{ bar: 'baz', qux: 'norf' }] }

let { foo: [{ bar: a }] } = myObject

console.log(a)                        // 'baz'

ES6

  • Can define default values if 'pulled' property is undefined
let myObject = { foo:'bar', baz: undefined }

let { baz = 'default' } = myObject

console.log(baz)                        // 'default'

ES6

Assignment destructuring

  • Can skip over items not cared about:
let [, x, y] = [1, 2, 3]

console.log(x)                        // 2
console.log(y)                        // 3

ES6

  • Similarly, for arrays:
let [x, y] = [1, 2, 3]

console.log(x)                        // 1
console.log(y)                        // 2

ES6

Assignment destructuring

  • Can destructure a function's parameter list
function displayNameAge ( {name, age} ) {
    console.log(`${name} is ${age} years old`)
}

displayNameAge( { age: 27, name: 'John'} )            // 'John is 27 years old'

ES6

Assignment destructuring

  • Possible use cases
  • Define default options for a method
function random() ( {min=1, max=100} ) {

    return Math.floor(Math.random() * (max - min) + min

}

console.log(random( {} ))                                // 67
console.log(random( { max: 20 } )                        // 11

ES6

Assignment destructuring

  • Possible use cases
  • Using regular expressions to break down longer strings and name them
function getUrlParts(url) {
  var regEx = /^(https?):\/\/(debtremedy.stepchange\.org)(\/page\/([a-z0-9-]+))$/
  return regEx.exec(url)
}

var parts = getUrlParts('http://debtremedy.stepchange.org/page/benefitIncome')
var [,protocol,host,pathname,slug] = parts
console.log(protocol)                                                            // 'http'
console.log(host)                                                                // 'debtremedy.stepchange.org'
console.log(pathname)                                                            // '/page/YourIncome'
console.log(slug)                                                                // 'YourIncome'

ES6

New features in ES6

Spread/rest parameters

  • Rest parameters
  • Creates an array out of a function's arguments
// Function calls
myFunction(...iterable);

// Array literals
[...iterable, 4, 5, 6]

// Destructing
[a, b, ...iterable] = [1, 2, 3, 4, 5];

  • ES5 - used 'arguments' variable to work with large numbers of arguments
function concat() {
    
    return Array.prototype.slide.call(arguments).join(' ')

}

var result = concat('Lorem', 'ipsum', 'dolor', 'sit', 'amet')

console.log(result)     // 'Lorem ipsum dolor sit amet'

Spread/rest parameters

ES5

  • Using rest parameter syntax
function concat(...words) {
    
    return words.join(' ')

}

var result = concat('Lorem', 'ipsum', 'dolor', 'sit', 'amet')

console.log(result)       // 'Lorem ipsum dolor sit amet'

ES6

  • If other parameters, rest param must be rightmost
function concat(prefix, suffix, ...words) {
    
    return prefix + words.join(' ') + suffix

}

var result = concat('START', 'END', 'Lorem', 'ipsum', 'dolor', 'sit', 'amet')

console.log(result)                                                  // 'START Lorem ipsum dolor sit amet END'

ES6

  • If 'arguments' was used, need to cut first two params by shifting

ES5

function concat() {
    
    var words = Array.prototype.slide.call(arguments);
    var prefix = words.shift();
    var suffix = words.shift();
    return prefix + words.join(' ') + suffix;

}

var result = concat('START', 'END', 'Lorem', 'ipsum', 'dolor', 'sit', 'amet')

console.log(result)                                                  // 'START Lorem ipsum dolor sit amet END'

Spread/rest parameters

Spread/rest parameters

  • Spread operator
  • Passes an array as arguments to a function
var missing = [4, 5, 6]

var sequence = [1, 2, 3, ...missing, 7, ,8 , 9]

ES6

  • Prefix array with '...'
  • To recap:
  • spread/rest operators both use '...' syntax
  • Rest operator extracts data (creates array from parameters)
  • Spread operator constructs data (inserts parameters from array)

New features in ES6

Promises

  • The Promise object is used for deferred and asynchronous computations. A Promise represents an operation that hasn't completed yet, but is expected in the future.
    
    new Promise(executor);
    new Promise(function(resolve, reject) { ... });
var promise = new Promise(function(resolve, reject) {
    
    // do something

    if( /* thing was done */ ) {
        resolve("Success");
    }
    else {
        reject(Error("Failed"));
    }

});

ES6

  • Takes one argument:
  • Do something with the callback (perhaps async)
  • Call resolve() if worked
  • Call reject() if not

callback with 'resolve' and 'reject' params

Promises

  • Creating a promise:
  • Using a promise:
promise.then(function(result) {
    // Action on success
}, function(err) {
    // Action on error
});

ES6

  • then() takes two arguments:

callback for success case

callback for failure case

function getData($timeout, $q) {

    return function() {

        var defer = $q.defer()

        // async function
        setTimeout(function() {
            if (Math.round(Math.random())) {
                defer.resolve('data received');
            }
            else {
                defer.reject('error');
            }
        }, 1000);
        
        return defer.promise
    }
}

AngularJS

Promises

  • Have existed non-natively for a while
  • May have used deferred objects in AngularJS
  • Deferred object exposes a promise and resolve/reject methods

Promises

  • Can chain 'then's to run async actions in sequence
getJSON('faq.json').then(function(faq) {
    return getJSON(faq.sectionUrl[0]);
}).then(function(section1) {
    console.log('Section 1: ' + section1);
}

ES6

  • Chaining
    • Promise.prototype.then(onFulfiled, onRejected)
    • Promise.all(iterable)
    • Promise.race(iterable)

New features in ES6

Proxies

  • The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).

    var p = new Proxy(target, handler);
  • target - object or function to wrap with Proxy.

  • handler - an object whose properties are functions which define the behavior of the proxy when an operation is performed on it.

Proxies

var target = {}
var handler = {}

var proxy = new Proxy(target, handler)

proxy.a = 'b'
console.log(target.a) // <- 'b'
console.log(proxy.c === undefined) // <- true
  • By default, proxies don’t do much – in fact they don’t do anything
    • no-op forwarding proxy

Proxies

var handler = {
  get (target, key) {
    console.info(`Get on property "${key}"`)
    return target[key]
  }
}

var target = {}
var proxy = new Proxy(target, handler)

proxy.a = 'b'
proxy.a    // <- 'Get on property "a"'
proxy.b    // <- 'Get on property "b"'
  • handler traps 
    • get (target, key)
    • set (target, key)
    • apply (target, f, args)
    • construct (target, args)

Proxies (example)

var handler = {
  get (target, key) {
    invariant(key, 'get')
    return target[key]
  },
  set (target, key, value) {
    invariant(key, 'set')
    return true
  }
}
function invariant (key, action) {
  if (key[0] === '_') {
    throw new Error(`Invalid attempt to ${action} private "${key}" property`)
  }
}

var target = {}
var proxy = new Proxy(target, handler)\

proxy.a = 'b'
console.log(proxy.a)  // <- 'b'
proxy._prop           // <- Error: Invalid attempt to get private "_prop" property
proxy._prop = 'c'     // <- Error: Invalid attempt to set private "_prop" property
  • Access control - deny access to private properties (prefixed with '_')

Proxies (example)

var validator = {
  set (target, key, value) {
    if (key === 'age') {
      if (typeof value !== 'number' || Number.isNaN(value)) {
        throw new TypeError('Age must be a number')
      }
      if (value <= 0) {
        throw new TypeError('Age must be a positive number')
      }
    }
    return true
  }
}
var person = { age: 27 }
var proxy = new Proxy(person, validator)
proxy.age = 'foo' // <- TypeError: Age must be a number
proxy.age = NaN   // <- TypeError: Age must be a number
proxy.age = 0     // <- TypeError: Age must be a positive number
proxy.age = 28
console.log(person.age) // <- 28
  • Validation - age validator

Proxies 

var target = {}
var handler = {}
var {proxy, revoke} = Proxy.revocable(target, handler)
proxy.a = 'b'
console.log(proxy.a) // <- 'b'
revoke()
console.log(proxy.a) // <- TypeError: illegal operation attempted on a revoked proxy
  • Revocable proxy
    • after revoke() is called, proxy is not usable anymore

What is TypeScript?

  • Superset of ES6
  • Includes all ES6 features
  • 'Syntactic sugar' for JavaScript
  • makes programs more readable
  • makes coding more intuitive
  • Worked on by Anders Hejlsberg, lead C# architect
  • May look more similar to C# / Java than JavaScript
  • Free, open source, Microsoft-developed
  • Translates ES6 features (classes, modules, etc.) into ECMAScript 5
  • Browser compatibility
  • Existing JavaScript programs are valid TypeScript programs
  • Designed to meet the needs of teams developing large JavaScript programs

What is TypeScript?

TypeScript: features

  • Variable type inferred from value
var x:number = 1
var n = 1                                         // var n:number = 1
console.log(typeof(n))            // number

var s = "Hello World"                // var s:string = "Hello World"
console.log(typeof(s))            // string

n = s;                                              // error
s = n;                                              // error

function f() {
    return "hello"
}

console.log(typeof(f.call()))    // string

TypeScript

Type inference/annotation

  • Unknown type?
    • Might not know data type provided by user or 3rd party library
    • use 'any' type to opt-out of type checking

Type inference/annotation

window.onmousedown = function(mouseEvent) { 
    console.log(mouseEvent.buton);  // <- Error  
};

window.onmousedown = function(mouseEvent: any) { 
    console.log(mouseEvent.buton);  // <- OK 
};

TypeScript

  • Best common type
    • When a type inference is made from several expressions it consider each candidate type and chooses one that is compatible with all the other candidates
var x = [0, 1, 2]; // number[]

var y = [0, '1', 2]; // any[]
 
var zoo = [new Rhino(), new Elephant(), new Snake()]; // Object[]

var zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()]; // Animal[]

Type inference/annotation

TypeScript

  • Can express return type for functions
function add(num1, num2): number {
    return num1 + num2;
}

function concat(str1, str2): string {
    return str1 + str2;   
}
  • 'void' type
    • absence of any type at all
    • used as return type of functions with no return value
function warnUser(): void {
    alert("This function doesn't return a type");
}

Type inference/annotation

  • Can express type requirement for parameters
function add(num1: number, num2: number) {
    return num1 + num2;
}

function concat(str1: string, str2: string) {
    return str1 + str2;   
}

TypeScript

  • Type checking immediately removes an entire class of bugs from codebase
  • Useful for large code-bases with teams working on different parts of a program

Type inference/annotation

  • Better toolings

TypeScript: features

  • Parameters always optional in JavaScript functions
  • TypeScript prohibits omitting typed arguments unless specified as optional ('?')
function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}

var result1 = buildName("Bob");  //works correctly now
var result2 = buildName("Bob", "Adams", "Sr.");  //error, too many parameters
var result3 = buildName("Bob", "Adams");  //ah, just right

TypeScript

Optional parameters

Default parameters

  • Optional parameter is essence a default parameter with value 'undefined'
function buildName(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName;
}

var result1 = buildName("Bob");  //works correctly now, also
var result2 = buildName("Bob", "Adams", "Sr.");  //error, too many parameters
var result3 = buildName("Bob", "Adams");  //ah, just right
function buildName(firstName: string, lastName = undefined) {
    return firstName + " " + lastName;
}

TypeScript: features

  • New datatype
enum Status { Ready, Waiting };
enum Color { Red, Blue, Green };
enum Polygon { Triangle=3, Square=4, Octagon=8 }

var status = Status.Ready;
status = Color.Green;  //error

var color = Color.Blue;    // 1
var colorName = Color[1]; // 'Blue'
var edges = Polygon.Octagon // 8

TypeScript

  • Enums are compatible with numbers, and numbers are compatible with enums

Enums

TypeScript: features

  • Define the required structure of an object
interface ILabelledValue {
    label: string;
}

function printLabel(labelledObj: ILabelledValue) {
    console.log(labelledObj.label)
}

var myObj = { size: 10, label: "Size 10 object" };
printLabel(myObj);
  • 'labelledObj' must satisfy requirement defined in interface (has string 'label' property) - duck-typing
  • can add other properties as long as required ones exist

Interfaces

  • optional properties ('?')
interface ILabelledValue {
    label: string;
    size?: number;
}

Interfaces

function printLabel(labelledObj: ILabelledValue) {
    console.log(labelledObj.label)
}

var myObj = { count: 10, label: "Size 10 object" };
printLabel(myObj);    // ok


var myObj = { size: 10, lbl: "Size 10 object" };
printLabel(myObj);    // error - myObj does not satisfy LabelledValue
  • extending other interfaces

Interfaces

interface IRectangle {
    x: number;
    y: number;
}
interface ICuboid extends IRectangle {
    z: number;
}
  • can extend multiple interfaces
interface IColor {
    r: number;
    g: number;
    b: number;
}
interface IColoredCuboid extends ICuboid, IColor{
    alpha: number;
}
interface ISearchFunc {
    (source: string, subString: string): boolean;
}
  • Function Types
  • param types need to match
var mySearch: ISearchFunc;

mySearch = function(src: string, sub: string) {
    var result = src.search(sub);
    if (result == -1) {
        return false;
    }
    else {
        return true;
    }
}

Interfaces

  • function 'signature'
interface IStringArray {
    [index: number]: string;
}

var myArray: IStringArray;
myArray = ["John", "Doe"];
  • Array Types

Interfaces

  • arrays have 'index' types - types allowed to index object
  • must be string or number
  • string-indexed arrays: dictionaries
interface IClock {
    currentTime: Date;
    setTime(d: Date);
}

class MyClock implements IClock {
    currentTime: Date;
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m:number) { }
}
  • Class Types

Interfaces

property

method

  • an interface can't have a constructor signature
    • constructor is a static method (class method)
    • interfaces are checked against instances
  • static vs instance variables/methods

Interfaces

  • static: one per class

shared by every Object of that Class

  • instance: one per Object

each Object of that Class has its own copy

  • when a class implements an interface, only instance side is checked
  • constructor is a static method
  • since it creates the instance, must be invoked before the instance is created

TypeScript: features

  • Similar to ES6
class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

var greeter = new Greeter("world");

Classes

  • Inheritance
class Animal {
    name:string;
    constructor(theName: string) { this.name = theName; }
    move(meters: number = 0) {
        alert(this.name + " moved " + meters + "m.");
    }
}


class Horse extends Animal {
    constructor(name: string) { super(name); }
    move(meters = 45) {
        alert("Galloping...");
        super.move(meters);
    }
}

var tom = new Horse("Tommy the Palomino");
tom.move(34); 

Classes

  • Private/Public modifiers
class Animal {
    private name:string;
    constructor(theName: string) { this.name = theName; }
}

class Cow extends Animal {
	constructor() { super("Cow"); }
}

var horse = new Animal("horse");
var cow = new Cow();

horse.name // error
cow.name // error

Classes

  • Static properties / functions
class Grid {
    static origin = {x: 0, y: 0};
    calculateDistanceFromOrigin(point: {x: number; y: number;}) {
        var xDist = (point.x - Grid.origin.x);
        var yDist = (point.y - Grid.origin.y);
        return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
    }
    constructor (public scale: number) { }
}

var grid1 = new Grid(1.0);  // 1x scale
var grid2 = new Grid(5.0);  // 5x scale

alert(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
alert(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));

Classes

TypeScript: features

Generics

  • Allow for creation of components that can work with a variety of types
function echo(arg: any): any {
    return arg;
}
  • This function returns the argument passed to it
  • It is 'generic' in that it will accept any type of argument passed to it
  • Will lose the type of object passed in (cast to any)

Generics

  • Can use a 'type variable' to capture type of argument passed in
function echo<T>(arg: T): T {
    return arg;
}
  • Expressed with <T> at end of function or property
  • This method truly 'generic' as it can use any type and not lose information about the type.

Generics

  • Compiler enforces correct use of generically typed parameters, e.g.
function echo<T>(arg: T): T {
    console.log(arg.length()); // error - arg might not have .length member
}
  • If we knew we only wanted the function to accept types with a .length() property:
interface IHasLength {
    length: number;
}

function echo<T extends IHasLength>(arg: T): T {
    console.log(arg.length());    // now we know arg has a length property
}

Generics

  • function will now not work for all types, though:
interface IHasLength {
    length: number;
}

function echo<T extends IHasLength>(arg: T): T {
    console.log(arg.length());               // now we know arg has a length property
}

echo(3);                                     // Error - number doesn't have .length property
echo({length: 5, value: 3});                 // OK

Generics

  • Generic classes
class GenericClass<T>{
    zeroValue: T;
    add: (x:T, y:T) => T;
}
  • Generic type parameter list in <> following class name
  • Putting type parameter on class itself gives all members of class access to it
  • Class has potential to work with variety of types
var myGenericNumberClass = new GenericClass<number>();
myGenericNumberClass.zeroValue = 0;
myGenericNumberClass.add = function(x, y) { return x + y };

var myGenericStringClass = new GenericClass<string>();
myGenericStringClass.zeroValue = "";
myGenericStringClass.add = function(x, y) { return x + y };

TypeScript: features

Type Compatibility

  • based on structural typing
    • a way of relating types based solely on their members.
interface Named {
    name: string;
}

class Person {
    name: string;
}

var p: Named;
// OK, because of structural typing
p = new Person();

Type Compatibility

  • Classes
    • When comparing two objects of a class type only members of the instance are compared. 
    • Static members and constructors do not affect compatibility. 
class Animal {
    feet: number;
    constructor(name: string, numFeet: number) { }
}

class Size {
    feet: number;
    constructor(numFeet: number) { }
}

var a: Animal;
var s: Size;

a = s;  //OK
s = a;  //OK

Type Compatibility

class Mammal {
    private name: string;
    constructor(_name: string) { 
        this.name = _name; 
    }
}

class Reptile {
    private name: string;
    constructor(_name: string) { 
        this.name = _name; 
    }
}


var cat = new Mammal('Cat');
var lizard = new Reptile('Lizard');
cat = lizard;   // error: Animal and Reptile are not compatible
  • Classes
    • private members - equivalent only when come from same declaration

Type Compatibility

class Mammal {
    private name: string;
    constructor(_name: string) { 
        this.name = _name; 
    }
}

class Dog extends Mammal {
    constructor() { 
        super("Dog"); 
    }
}

var cat = new Mammal('Cat');
var dog = new Dog();
cat = dog;                        // OK
  • Classes
    • private members - equivalent only when come from same declaration

Type Compatibility

var x = (a: number) => 0;
var y = (b: number, s: string) => 0;
var z = (s: string) => 0;

y = x; // OK - discarding s: string
x = y; // Error
y = z; // Error
x = z; // Error
z = x; // Error
  • Functions
    • checks corresponding compatible parameters
    •  ‘discarding’ parameters is allowed - it is quite common in JS

Type Compatibility

interface Empty<T> {
}
var x: Empty<number>;
var y: Empty<string>;

x = y;  // okay, y matches structure of x
  • Generics
    • type parameters only affect the resulting type
interface NotEmpty<T> {
    data: T;
}
var x: NotEmpty<number>;
var y: NotEmpty<string>;

x = y;  // error, x and y are not compatible

TypeScript: features

Mixins

  • Classes can be built up from simpler partial classes
class Join() {
    function concat (...words) {
        return words.join(' ')
    }
}

TypeScript

class Split {
    function split (word) {
        return word.split(' ');
    }
}

TypeScript

class StringOperations implements Join, Split {
    
    prefix(word) {
        return "START" + word;
    }
    
    // Join
    join: (string[]) => string;
    // Split
    split: (string) => string;
}

TypeScript

Mixins

TypeScript

class StringOperations implements Join, Split {
    
    prefix(word) {
        return "START" + word;
    }
}
  • Instead of using 'extends', uses 'implements'
  • Treats classes as interfaces, so only member types used rather than implementations
  • This would usually mean providing implementation in-class  - but this is what we want to avoid using Mixins
class StringOperations implements Join, Split {
    
    prefix(word) {
        return "START" + word;
    }
    
    // Join
    join: (string[]) => string;
    // Split
    split: (string) => string;
}

applyMixins(StringOperations, [Join, Split]);
  • To satisfy the need for implementations, we create stand-in properties for partial method members

Mixins

  • Then use a helper function to do mixing for us
//
// Runtime library
//

function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
            derivedCtor.prototype[name] = baseCtor.prototype[name];
        })
    }); 
}

TypeScript: features

Declaration merging

  • Typescript merges any number of declarations with same name into a single defintion
  • Merging interfaces and modules with classes, functions and Enums
interface IShape {
    positionX : number;
    positionY: number;
}

TypeScript

interface IShape {
    height: number;
    width: number;
}

TypeScript

var shape: IShape = { positionX: 10, positionY: 5, height: 20, width: 15 }

TypeScript

Declaration merging

  • Interface merging
  • Merges the members of both declarations into a single interface with same name

Declaration merging

  • Interface merging
  • Members of the interfaces must be unique or the compiler will error, except:
  • Function members of the same name are treated as overload methods
interface Document {
    createElement(tagName: any): Element;
}
interface Document {
    createElement(tagName: string): HTMLElement;
}
interface Document {
    createElement(tagName: "div"): HTMLDivElement; 
    createElement(tagName: "span"): HTMLSpanElement;
    createElement(tagName: "canvas"): HTMLCanvasElement;
}

TypeScript

interface Document {
    createElement(tagName: "div"): HTMLDivElement; 
    createElement(tagName: "span"): HTMLSpanElement;
    createElement(tagName: "canvas"): HTMLCanvasElement;
    createElement(tagName: string): HTMLElement;
    createElement(tagName: any): Element;
}

TypeScript

Declaration merging

  • Interface merging
  • overloads declared later take precedence

Declaration merging

  • Module merging
  • Similarly, modules of same name will merge members into one module
module Shape {

    export interface IDimensions { width: number, height: number }
    
    export class Square { }
    export class Rectangle { }
    export class Triangle { }

}
module Shape {
    
    var pi = 3.1415926
        
    export function calculateArea( radius: number ) {
        return radius * radius * this.pi
    }

    export interface IDimensions { radius: number }
    
    export class Circle { }

}

TypeScript

TypeScript

module Shape {
        
    export function calculateArea( radius: number ) {
        return radius * radius * this.pi
    }

    export interface IDimensions { width: number, height: number, radius: number }
    
    export class Square { }
    export class Rectangle { }
    export class Triangle { }
    export class Circle { }

}

TypeScript

Declaration merging

  • Module merging

Declaration merging

  • Module merging
module Shape {
        
    export function calculateArea( radius: number ) {
        return radius * radius * this.pi
    }

    export interface IDimensions { width: number, height: number, radius: number }
    
    export class Square { }
    export class Rectangle { }
    export class Triangle { }
    export class Circle { }

}

TypeScript

  • Interfaces merged
  • Not desirable, e.g. radius not valid for square
  • 'pi' variable not exported so not visible in merged module
  • exported function will error

TypeScript: features

Declaration files

  • a .d.ts file required when using an external JavaScript library or new host API
  • declaration file exposes the 'shape' of that library to TypeScript
  • exposed objects, functions, classes, interfaces
  • many popular libraries will have a definition file you can use

Declaration files

var Geometry = {

    point: (function() {
        function point(x, y) {
            this.x = x;
            this.y = y;
        }
        return point;
    }){},

    line: (function() {
        function line(p1, p2) {
            this.point1 = {x: p1.x, y = p1.y};
            this.point2 = {x: p2.x, y = p2.y};
        }
        return line;
    }){},

    distance: function (line) {
        var p1 = line.point1;
        var p2 = line.point2;
        return Math.sqrt(Math.pow(p2.x - p1.x, 2) +
            Math.pow(p2.y - p1.y, 2)
        };

};

Geometry.js

declare module MGeometry {

}
  • Constructing Geometry.d.ts
  • A module declaration:
declare module MGeometry {

    export interface Main {
        
    }

}
  • An interface importable into the TypeScript file:

Declaration files

var Geometry = {

    point: (function() {
        function point(x, y) {
            this.x = x;
            this.y = y;
        }
        return point;
    }){},

    line: (function() {
        function line(p1, p2) {
            this.point1 = {x: p1.x, y = p1.y};
            this.point2 = {x: p2.x, y = p2.y};
        }
        return line;
    }){},

    distance: function (line) {
        var p1 = line.point1;
        var p2 = line.point2;
        return Math.sqrt(Math.pow(p2.x - p1.x, 2) +
            Math.pow(p2.y - p1.y, 2)
        };

};

Geometry.js

  • The properties/functions exposed by the library:
declare module MGeometry {

    export interface Main {
        point:Point;
        line:Line;
        distance(line:Line):number;    
    }

}
  • Interfaces describing shape of individual members:
    export interface Point {
        x:number;
        y:number;
        new(x:number, y:number):Point;
    }

    export interface Line {
        point1:Point;
        point2:Point;
        new(p1:Point, p2:Point):Line;
    }

Declaration files

var Geometry = {

    point: (function() {
        function point(x, y) {
            this.x = x;
            this.y = y;
        }
        return point;
    }){},

    line: (function() {
        function line(p1, p2) {
            this.point1 = {x: p1.x, y = p1.y};
            this.point2 = {x: p2.x, y = p2.y};
        }
        return line;
    }){},

    distance: function (line) {
        var p1 = line.point1;
        var p2 = line.point2;
        return Math.sqrt(Math.pow(p2.x - p1.x, 2) +
            Math.pow(p2.y - p1.y, 2)
        };

};

Geometry.js

  • Perhaps give the interface a more friendly name
declare module MGeometry {

    export interface Main {
        point:Point;
        line:Line;
        distance(line:Line):number;
    }

    export interface Point {
        x:number;
        y:number;
        new(x:number, y:number):Point;
    }

    export interface Line {
        point1:Point;
        point2:Point;
        new(p1:Point, p2:Point):Line;
    }

    declare var Geometry:MGeometry.Main;

}

End

Introduction to WebComponents

David Kane

What are WebComponents?

  • A way of writing your own HTML tags and behaviours
  • Using WebComponents a project might look like this:
<html>

<head>
    ......
</head>

<body>
    <app-header>
        <li link="/home" class="active">Home</li>
        <li link="/about">About</li>
        <li link="/contact">Contact</li>
    </app-header>

    <app-filterlist>
        <p>Item 1</p>
        <p>Item 2</p>
        <p>Item 3</p>
        <p>Item 4</p>
        <p>Item 5</p>
    </app-filterlist>

    <app-fatfooter>
        <section>
            <li link="/linkedin" class="active">LinkedIn</li>
            <li link="/google">Google</li>
            <li link="/facebook">Facebook</li>
        </section>
        <section>
            <li link="/blog" class="active">Blog</li>
            <li link="/youtube">Youtube</li>
        </section>
    </app-fatfooter>
</body>

</html>
        

What are WebComponents?

What are WebComponents?

  • Easier to visually parse
  • The custom tags represent re-usable bits of code
  • Code can be reused over multiple pages or projects
  • Cuts down on repeated sections of boilerplate code

What are WebComponents?

  • e.g. for a navbar in Bootstrap, need to copy/paste:

for each page!

<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
    <div class="navbar-header">
        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex6-collapse">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
        </button>
        <a class="navbar-brand" href="#">Brand</a>
    </div>
    <div class="collapse navbar-collapse navbar-ex6-collapse">
        <ul class="nav navbar-nav">
            <li class="active"><a href="#">Home</a></li>
            <li><a href="#">About</a></li>
            <li><a href="#">Contact</a></li>
        </ul>
    </div>
</nav>

What are WebComponents?

  • 4 main elements to WebComponents spec:
  • HTML Templates
  • Shadow DOM
  • Custom Elements
  • HTML Imports

WebComponents specification

HTML Templates

  • Take the form <template></template>
  • 'Custom tag'
  • Any code inside this tag is just parsed, not rendered

HTML Templates

  • Example: HTML5 audio controls wrapped in a <template></template> tag
<template id="myTemplate">
    <img src="image.png">
    <audio controls>
        <source src="audio.ogg" type="audio/ogg">
        <source src="audio.ogg" type="audio/mpeg">
        Your browser does not support the audio tag.
    </audio>
</template>

HTML

  • Were this in the DOM the audio controls would not be rendered

HTML Templates

  • Content inside <template> tags hidden from the DOM
  • Wouldn't show up in 'Elements' view in browser tools by default
  • Couldn't find <img> tag within the <template> using document.querySelectorAll("template.img")

WebComponents specification

Shadow DOM

  • Allows browser to include subtrees of DOM elements not in main document DOM tree
  • Shadow DOM not visible in 'light' DOM by default
  • Can peek into an element's Shadow DOM using Chrome dev tools:

Shadow DOM

Shadow DOM

  • Restricts visibility and accessibility of component's undelying code
  • Encapsulates markup, styles and scripts according to OO principles
  • Before ShadowDOM, only way to isolate parts of HTML code was iFrames

Shadow DOM

  • Terminology
  • A document tree is a node tree (DOM) whose root node is a document
  • A shadow host is an element hosting one or more hidden node trees, or shadow trees
  • A shadow root is the root node of a shadow tree
  • If more than one shadow tree under same shadow host, more recently added shadow tree is the younger shadow tree
  • Less recently added one is the older shadow tree
  • Root nodes of these are the younger shadow node and older shadow node

Shadow DOM

  • Terminology
  • The shadow boundary is the boundary between what we can see in the DOM and the invisible implementation detail

Shadow DOM

  • Example: adding a Shadow DOM to an element
<!-- HTML holder -->
<div id="myMessage">
    <h1>
        This is a message
    </h1>
</div>

<!-- Template -->
<template id="message">
    <h1>
        This is a new message
    </h1>
</template>

HTML

page output

  • Since contents of <template> are not parsed as HTML, only content of HTML holder shows

Shadow DOM

<!-- HTML holder -->
<div id="myMessage">
    <h1>
        This is a message
    </h1>
</div>

<!-- Template -->
<template id="message">
    <h1>
        This is a new message
    </h1>
</template>

HTML

  • Example: adding a Shadow DOM to an element
var host = document.querySelector('#myMessage');

var root = host.attachShadow(open);

var template = document.querySelector('#message');

root.appendChild(template.content);

JavaScript

  • Create a shadow root on the 'myMessage' DOM element
  • Append template content to that shadow root

Shadow DOM

  • Example: adding a Shadow DOM to an element

Resultant DOM:

page output

  • Original content of div no longer showing
  • Shadow root takes precedence

Shadow DOM

  • Example: adding a Shadow DOM to an element
<!-- HTML holder -->
<div id="myMessage">
    <h1 slot="heading">
        This is a message
    </h1>
    <div slot="subheading">
        This is another message
    </div>
</div>

<!-- Template -->
<template id="message">
    <div style="color:red;">
        <slot name=".heading"></content>
    </div>
    <h2>
        <slot name=".subheading"></content>
    </h2>
</template>

HTML

var host = document.querySelector('#myMessage');

var root = host.attachShadow(open);

var template = document.querySelector('#message');

root.appendChild(template.content);

JavaScript

  • Can use HTML holder's original content
  • <slot> tag used to grab contents of original div with corresponding slot property (previously <content> tag)

Shadow DOM

  • Example: adding a Shadow DOM to an element
  • <slot> tag wrapped in selectors that change presentation of content

page output

<!-- HTML holder -->
<div id="myMessage">
    <h1 slot="heading">
        This is a message
    </h1>
    <div slot="subheading">
        This is another message
    </div>
</div>

<!-- Template -->
<template id="message">
    <div style="color:red;">
        <slot name=".heading"></slot>
    </div>
    <h2>
        <slot name=".subheading"></slot>
    </h2>
</template>

HTML

Shadow DOM

  • Visualization
  • When a new shadow root created and appended to DOM:

Shadow DOM

  • Visualization
  • As rendered:

Shadow DOM

  • Events
  • Events fired from within shadow tree can be listened to in the document.
  • The source of the event would be logged as the <audio> element itself, rather than button inside it 
  • e.g. 'mute' button in an <audio> HTML5 element is clicked, event listener on an enclosing div would hear it
<div onclick="alert('fired by: ' + event.target)">
    <audio controls src="test.wav"></audio>
</div>

HTML

  • Events 'retargeted' when crossing shadow boundary to avoid exposing internals of shadow subtree

Shadow DOM

  • Events
  • Some events never cross the 'shadow boundary':
  • abort
  • scroll
  • load
  • select
  • error
  • change
  • reset
  • resize
  • selectstart

Shadow DOM

  • Shadow DOM and CSS
  • Selectors don't cross the shadow boundary
  • Styles defined in shadow tree are scoped to that tree
<div><h3>Light DOM</h3></div>

HTML

var root = document.querySelector('div').attachShadow(open);
root.innerHTML = '<style>h3{ color: red; }</style>' + 
                 '<h3>Shadow DOM</h3>';

JavaScript

page output

  • This allows styles to be fully encapsulated within a component

Shadow DOM

  • Shadow DOM and CSS
  • Can 'include' stylesheets within templates:
<template>
    <style>{{ include: "./normalize.css" }}</style>
    <style>{{ include "./layout-utils.css }}</style>
    <div>
        <!-- template elements -->
    </div>
</template>

HTML

Shadow DOM

  • Shadow DOM and CSS
  • There were a few options to allow host document CSS to pierce the shadow boundary
  • These have since been removed (against principles of style encapsulation?)

Shadow DOM

  • Shadow DOM and CSS
  • Can uss CSS variables to create placeholders in Shadow DOM

HTML

<style>
    #host {
        --button-text-color: green;
        --button-font: "Times New Roman", Georgia, Serif;
    }
</style>
<div></div>

<script>
var root = document.querySelector('div').attachShadow(open);
root.innerHTML = '<style>' + 
                 'button {' +
                    'color: var(--button-text-color, blue);' +
                    'font-family: var(--button-font);' +
                 '{' +
                 '</style>' +
                 '<content></content>';
</script>

Shadow DOM

  • Shadow DOM and CSS
  • If we are using <slot> to pull content from light DOM to Shadow DOM, retains styling from light DOM
  • We can override this from within the Shadow DOM using ::slotted pseudo-element

page output

  • Shadow DOM and CSS
<div>
  <h3>Light DOM</h3>
  <section>
    <div>I'm not underlined</div>
    <p>I'm underlined in Shadow DOM!</p>
  </section>
</div>
var div = document.querySelector('div');
var root = div.attachShadow(open);
root.innerHTML = '<style>' +
      'h3 { color: red; }' +
      'content[select="h3"]::slotted > h3 {' +
        'color: green;' +
      '}' +
      '::slotted section p {' +
        'text-decoration: underline;' +
      '}' +
    '</style>' +
    '<h3>Shadow DOM</h3>' +
    '<slot name="h3"></slot>' + 
    '<slot name="section"></slot>';

HTML

JavaScript

Shadow DOM

WebComponents specification

Custom Elements

  • At its simplest level, HTML templates + Shadow DOM
  • Allows developers to define new types of HTML element
  • Name should always contain a hyphen
  • e.g. <my-element></my-element>
  • distinguishes them from native HTML elements

  • ensures new elements (HTML6+) will not have same name as any custom elements

Custom Elements

  • Use document.registerElement() to register a new element

var message = document.registerElement('my-message', {
    // Inherits from base HTMLElement
    prototype: Object.create(HTMLElement.prototype)
});

JavaScript

  • It may inherit from existing elements, e.g.

var message = document.registerElement('custom-button', {
    prototype: Object.create(HTMLButtonElement.prototype),
    extends: 'button'
});

JavaScript

  • This will create a <custom-button></custom-button> element for use in HTML

  • Will have all features of a <button> tag, which can be added to

Custom Elements

  • Initialize element, adding child nodes:

var message = new message();
document.body.appendChild(message);

JavaScript

  • Lifecycle methods:

  • createdCallback()

when an instance of the custom element is created

  • attachedCallback()

when the instance is attached to the DOM

  • detachedCallback()

when the instance is removed from the DOM

  • attributeChangedCallback()

when an attribute of the instance is added, updated or removed

WebComponents specification

HTML Imports

  • After creating new custom tag, make code reusable by writing definition inside a HTML file
  • Using HTML imports, can include this definition on all pages using custom tag
<html>
<head>
    <link rel="import" href="/templates/my-message.html">
</head>
<body>
    <my-message></my-message>
<body>
</html>

HTML

  • Usage:

HTML Imports

<script async>
    function handleLoad(e) {
        console.log('Loaded import: ' + e.target.href');
    }
    function handleError(e) {
        console.log('Error loading import: ' + e.target.href');
    }
</script>

<link rel="import" href="/templates/my-message.html"
      onload="handleLoad(event)" onerror="handleError(event)">

HTML

  • Might include some script to handle errors while importing:

WebComponent example project

WebComponents resources

The Jackal of Javascript (pretty exhaustive):

WebComponents.org (various resources):

Introduction to ECMAScript 6

By AdapTeach

Introduction to ECMAScript 6

  • 1,461