Wednesday 20 July 2016

TypeScript

100% Design Activities @ Lynda - 


TypeScript Jess Chadwick

Dfn
  • Transpilers - Compile TypeScript (offers Static Typing) syntax to compatible JavaScript
Installation

npm install -g typescript
npm install -g lite-server

Compile from TypeScript to JavaScript and Watch for changes automatically
tsc -w ./app.ts

TypeScript Configuration File with compiler options
  • Notes:
    • tsc compiler detects tsconfig.json file and treats files and containing folders as a project (and detects .ts files to compile)
// change the JS that TS generates
{
  "compilerOptions": {
    "target": "es5"
  }
}


ECMA6 Features


// Optional parameters (default values specified when expected parameter omitted)
function myFunc(param1 = 0) { ... }

// Define a String Template using Backtick that may contain any string literal. JS expressions for rendering must be wrapped within interpolation ${..}.


// Template Strings (syntactic sugar to construct a string containing values of object variables
myObjectLiteral = {
  id: 1
  name: "Luke"
}

container.innerHTML = `
  <div class='${myObjectLiteral.name}'></div>
`
// Variables and Constants
let a: string = "Luke";
const b: string = "Luke2";

// Loops

// ES6 allows iteration over values directly
for (var value in array) {
  console.log(`${value}`);
}

// Previously we iterated over the indexes of the array
for (var index in array {
  var value = array[index];
  console.log(`${index}: ${value}`);
}


// Arrow Functions (aka Lambda Expressions) so not to lose context of 'this'

// convert from JavaScript Function to an Arrow Function by removing the 'function()' keyword, and replacing it with '() =>'

this.count = 0;

el.addEventListener('click', () => {
  console.log(`${_this}`);
});

// Previously we would save a reference to the scope of this outside of the function where we have access to it to ensure we have correct scope context

this.count = 0;
let _this = this.count;

el.addEventListener('click', function() {
  console.log(`${_this}`);
});

// Now
var filtered = [1, 2].filter((x) => x > 0)

// Prev
var filtered = [1, 2].filter((x) => {
  return x > 0;
}


// Destructuring (assign multiple values with single statement)
// eg1
var array = [1, 2];
var [first, second] = array;


// eg2
var a = 1, b = 2;
[a, b] = [b, a];


// eg3
function getData(param) {
  var myObj = { score: 1, b: 2, c: param };
  return myObj;
}
var { score: isScored, b, c } = myObj(3); // map variable score to custom isScored

// eg4 - apply Destructuring to object parameter, using optional parameters for default values, and assign to local variable
function myFunc({ 
  start: start = 0, 
  end: end = 10, 
  mid, 
  start: current }) {
  console.log(current);
}


// Spread Operator ()
// eg1 - pass in any number of arguments into a function
function myFunc(action, ...values) { }
myFunc('subtract', 1, 3, 6, 3);
// eg2
var a = [1, 2], b = [3, 4];
a.push(b);

// Computed Properties ()
// eg1 - alternative to string concatentation for naming conventions
const myPrefix = "ls ";

var myVar = {
  [myPrefix + "Home"]: isActive("Home"),
  [myPrefix + "Mobile"]: isActive("Mobile")
}

function isActive(location) {
  return Math.random() >= 0.5;
}

Static Analysis and Type Inference is performed by TypeScript (to inform user of Static Typing related issues)

// the 'any' type (unrestricted Dynamic Type) is inferred when TypeScript unable to determine the type automatically but TypeScript does not provide Static Typing protection when 'any' is used (i.e. even typos)
function myFunc(a: any[], b: any): any { }

Union Types (allow more than one type of arguments to be accepted)
function myFunc(a: (string | any[]), b: any) { }

// Type Guard Syntax
if ( a instanceof Array) {
  a.push('abc');
}

// Type Guard Syntax (reserved for Primitive Types)
if ( typeof a === 'string') {
  a.substr(1);
}


Anonymous Type instead of Union Types to allow any object Type as Parameter that has a length property 
function myFunc(a: { (property) length: number }, b: { (property) length: number }) { }


Generic Constraints (Use Generic Type Parameters with Constraints that force consumers to pass in values of the same type when there are multiple parameters in order to prevent operations that do not make sense such as addition of a string length and an array length)

  • Replace the Anonymous Types with Generic Types, and apply Generic Constraints that may be applied to Generic Type Parameters to restrict the kinds of values satisfying those parameters by following the parameter with the extends keyword followed by the type (whether it be in the form of an Anonymous Type, Interfaces, Classes, or even Primitive Types) describing the constraint to apply (i.e. to only accept objects with a length field).
  • Note that any type that is compatible with type T may be used, including those Inheriting from it

function myTotalFunc<T extends { length: number }>(a: T, b: T) { 
  var myTotal: number = a.length + b.length;
  return myTotal;
}
var length = myTotalFunc('Luke', [1, 2])

// alternative using an Interface for the Generic Constraint
interface IConstrainLength {
  length: number;
}

function myTotalFunc<T extends IConstrainLength>(a: T, b: T) { 
...

// alternative using a custom class that extends/inherits from the Array class, and so it is an instance of the Array class and so it matches the same Generic Type Parameter T
class CustomArray<T> extends Array<T> {}

function myTotalFunc<T extends IConstrainLength>(a: T, b: T) { 
...
}
var length = myTotalFunc([1, 2], new CustomArray<number>())


Overloaded Functions (expose multiple method signatures and add them on top of the action function implementation, but when using the function with TypeScript the syntax hints does only show the two different overload functions, but not the option to call the underlying function that accepts one or the other). Note that JS only allows a single implementation of a function.
function myFunc(a: string, b: string): any { }
function myFunc(a: any[], b: any[]): any { }
function myFunc(a: any[], b: string): any { }
function myFunc(a: (string | any[]), b: (string | any[]) ): any {
}

Custom Types and Interfaces to describe Objects and Classes (in statically typed languages this is a contract describing data and behaviour that is exposes. interfaces are used strictly for compile-time checks but have no runtime effect)
// Interface defining data structure of an object
interface MyClass {
  name: string;
  phone?: number; // Optional property that when defined must be of number type
}
// Interface with methods for a service
interface MyServiceClass {
  add(todo: Todo): Todo;
  delete(todoId: number): void;
  getAll(): Todo[];
  getById(todoId: number): Todo;
}
var me: MyClass = { name: "Luke" };
var me: MyClass = { };  // error since object literal not compatible with MyClass type
var me = <MyClass>{}; // alternative is Casting Syntax to define type of the object itself instead of the type of the variable to achieve the same thing

Interfaces to describe Functions (extended jQuery interface owned by others without modifying original definition with added data structures and properties to an additional interface definition without conflict)

interface MyData {
   name: string;
}
interface jQuery {
   (selector: string | any): jQueryElement;
   version: number;
   fn: any;
}
interface jQueryElement {
   data(name: string): any;
   data(name: string, data: any): jQueryElement;
}
 // extend jQuery interface owned by others without conflicts or modifying original definition
interface jQueryElement {
  myData(): MyData;
  myData(myData: MyData): jQueryElement;
}
$.fn.myData = function(myData?: MyData): MyData {
   if(myData) {
      $(this).data('myData', myData);
   } else {
      return $(this).data('myData');
   }
}
var myData = { name: "Luke" };
var $ = <jQuery>function(selector) { // retrieve DOM element };
$.version = 2;
var element = $('#domId); // use myDom function to get a reference to DOM element
element.data('myData', myData);  // set MyData object to data property
var savedMyData = element.data('myData');  // retrieve value that was just set using overload data property name
element.myData(myData);  // methods 'data' and 'myData' from both original and extended jQuery interfaces available for usage in autocompletion

Interfaces implemented by Classes (to achieve expected behaviour)

  • Note: TypeScript ensures that an object's structure that is explicitly connected to an interface matching that defined interface. This is enforced by using the 'implements' keyword. A Class may implement Multiple Iterfaces (all listed after the 'implements' keyword)
interface IIdGenerator {
   nextId: number;
}

class MyService implements IMyService, IIdGenerator {
...

Enums for Constant Value (Meaningful) definitions

  • Instead of using a type such as Boolean (limited to True or False) we can define a Custom Type with multiple different States (such as New, Deleted, Active, Inactive) and use say a corresponding Number type to indicate the state it is in
interface MyObject {
   name: string;
   state: MyObjectState;
}

// dynamically access properties with MyObjectState[1], MyObjectState.New
enum MyObjectState {
   New = 1,
   Deleted,
   Active,
   Complete
}
function delete(myObject: MyObject) {
   if(myObject.state != MyObjectState.Complete) {
     console.log("Cannot delete as incomplete");
   }
}
var myObject: MyObject = { 
   state: MyObjectState.Completed 
};

Getter and Setter Methods (for Property) to enforce Workflow Logic for Enum states (i.e. cannot change state unless Active or Completed)

  • Note: Either individually or both Getter and Setter Methods may be used
  • Note: Generally we would apply Getter and Setter Methods to a Class (abstracted into a Service) not just a simple object literal

class MyObject {
   _state: MyObjectState;
   name: string;

   get state() {
      return this._state;
      return MyObject.Complete;
   },
   // assign to dynamically generated variable that both getter and setter have access to
   set state(newstate) {
      // add validation logic here that must be satisfied in order to set a new state

      this._state = newState;
   }

   constructor(name: string) {
      this.name = name;
   }
};
var myObject = new MyObject("luke");

// get internal variable
myObject.state
// set internal variable
myObject.state = MyObjectState.Complete;


State Machine Design Pattern (implement logic for different state changes as their own separate abstract class)

  • Note: Share as Base Class and derive logic from Inheriting / Extending Classes


// state changer class with workflow to change state
class MyObjectStateChanger {
  // end state accepted as a parameter
  constructor(private newState: MyObjectState) { }

  // validate whether change state permitted to occur
  canChangeState(myObject: MyObject): boolean {
     return !!myObject;
  }

  // define shared behaviour in this method. 
  // check if permitted to change state and then update state of object instance
  changeState(myObject: MyObject) {
     if(this.canChangeState(myObject)) {
        myObject.state = this.newState;
     }
     return myObject;
  }
}

  • Extended (Inherit from) Class (extend and override behaviour of base class)
    • Note: JS and ES6 do not support defining and inheriting from Abstract Base Classes (i.e. Base Classes that are only defined to act as Base Classes and are not instantiated by themselves). TypeScript does support Abstract Base Classes
class CompleteMyObjectStateChanger extends MyObjectStateChanger {
    // inherits everything. do not need to declare constructor on derived class.
    // if add constructor on extended class, it must be called on the constructor of the Base Class (by adding a call to the super function)
   // we pass the state 'Complete' (since we always want to change the state to complete in this derived class)
   constructor() {    
      super(MyObjectState.Complete);
   }

    // override/extend logic of method in base class. here 'super' is the base class object (not just its constructor)
   canChangeState(myObject: MyObject): boolean {
      return super.canChangeState(myObject) && (myObject.state == MyObjectState.Active || myObject.state == MyObjectState.Deleted)
   }
}
var completeMyObjectStateChanger = new CompleteMyObjectStateChanger(MyObjectState.Complete);

Abstract Base Class (encapsulation and inheritance of abstract base class. for when intent is only for the base class to serve as a base class, but not to be instantiated directly, we express that intent by applying prefixing class with the word 'abstract')
  • Abstract Methods may be expressed by prefixing the method name with 'abstract' - force all classes deriving from this class to fully implement their own canChangeState method and associated logic from scratch. the implementation of the method in the 'abstract' method is therefore not required (and references to canChangeState in extensions of classes that derive from the abstract class must remove reference to 'super' since the 'abstract' method may only base used as a 'base' class)
// abstract base class serves as base class and can be derived from but cannot be instantiated directly
abstract class MyObjectStateChanger {
   // protected access modifier causes 'newState' to be accessible in constructor of inheriting and extending classes such as CompleteMyObjectStateChanger class (i.e. using 'this.newState')
   constructor(protected newState: MyObjectState) { ...

   // method that defines shared behaviour
   abstract canChangeState(myObject: MyObject): boolean {
     return !!myObject;
  }

   changeState(..
}
new MyObjectStateChanger();


Traditional JS Prototypical Inheritance and Custom Types using Classes .(for implementing Prototypical Inheritance, Abstraction, and Encapsulation)

  • Note: JS based on objects and prototypical inheritance (prototype-based programming)
  • Note: ES6 (not JS) introduces the concept of a Class (syntactic sugar)
  • In example below calling myCustomObject.toString() would result in JS searching for method up the hierarchy until that method found on an object
  • this refers to the instance of a created object

// Define behaviour on Prototype object for sharing it b/w object instances. Other object instances are linked to that Prototype object in the inheritance hierarchy
Object.prototype object with method(s)
myCustomObject.prototype // prototype object with method(s)
var myCustomObject = {}; // object literal instance that may have method(s). automatically has its Prototype object linked to Object.prototype using a Constructor (just initialises function using the new keyword)

Function.prototype function with method(s) i.e. bind
myCustomFunction.prototype // prototype function
var myCustomFunction = function() {};

Array.prototype function with method(s) i.e. slice
myCustomArray.prototype // prototype array
var myCustomArray = []; // 

// JS syntax
function MyService() {
   this.myArray = [];
}
MyService.prototype.getAll = function() {
   return this.myArray;
}
// create new instance and call methods defined on prototype like they were defined on the instance itself
var service = new MyService();
service.getAll();

TypeScript Class and Private Constructor Parameters are automatically assigned to a Class (use ES6 to define same behaviour as it compiles to JS prototype inheritance syntax)


// ES6 TypeScript syntax equivalent with class having constructor and functions
class MyService {
   // condense into single expression by defining class parameter and constructor property in same operation by prefixing with private access modifier
   constructor(private myArray: MyArray[]) {

   }

   // combine definition and initialisation of property into single operation
   myArray: MyArray[] = [];
   // define class parameter 
   constructor(myArray: MyArray[]) {
      this.myArray = myArray; 
   }
   getAll() {
      return this.myArray;
   }
}



Static Properties (aka Static Members) for use across multiple component instances (i.e. single variable to track attached to object instance that will use it)
  • Note: Traditionally declared in global namespace (bad practice)
  • Static Property should be avoided, but use as last resort to make variable available across entire application
  • Static Methods used to centralise common logic across multiple components

Access Modifiers (applicable to static and non-static Class properties and methods to prevent them from being accessed outside class)
  • Note: When applying 'private' to getters and setters they both must be configured the same
  • Private Modifier most restrictive as member only accessibly by other methods on same class definition (even classes that inherit or extend are restricted from access)
  • Protected Modifier is such that only methods defined on same class may access member + any classes that inherit or extend from the class
  • Public Modifier (Default) may be accessed from any other type
  • IMPORTANT NOTE: JS doesn't support Private members since JS objects are just dynamic properties that are accessible by everyone who can access the object so use of the 'private' keyword during development is only to express intent. Another convention is applying underscores in from of Private Property variables

// JS attach to object instance constructor function
function MyService() {
}
// static property
MyService.lastId = 0;
// static method
MyService.getNextId = function() {
   return MyService.lastId += 1;
}
MyService.prototype.add = function(myArray) {
   // access static method
   var newId = MyService.getNextId();
}


// ES5 equivalent
class MyService() {
   // static property available across entire application (like a global variable)
   private static _id: number = 0;

   private get nextId() {
      return MyService.getNextId();
   }

   private set nextId(nextId) {
      MyService._lastId = nextId - 1;
   }

   constructor(private myArrays: MyArray[]) {}

   add(myArray: MyArray) {
      var newId = MyService.getNextId();

      this.id = this.nextId;
      this.myArrays.push(myArray);
      return myArray;
   }

   delete(myArrayId: number): void {
      var toDelete: this.getById(myArrayId);
      var deletedIndex = this.myArrays.indexOf(toDelete);
     this.myArrays.splice(deletedIndex, 1);
   }

   getAll(): MyArray[] {
      // prevent consumers from manipulating the myArrays by controlling changes 
      // through cloning a copy of them, serialising and them parsing the clone
      var clone = JSON.stringify(this.myArrays);
      return JSON.parse(clone);
   }

   getById(myArrayId: number): MyArray {
       // filter the list down to ids matching myArrayId using built-in array method filter
      var filtered = this.myArray.filter(x => x.id == myArrayId);
      // retrim if find at least one match
      if(filtered.length) {
         return filtered[0];
      }
      return null;
   }

  private getAll() {
     return this.myArray;
  }
  // static method to centralise common logic across multiple components
  static getNextId() {
     return MyService.lastId += 1;
  }
}

Generics applied to Functions (create functions and classes defining reusable behaviour across multiple types whilst retaining full info about the type)
  • Given a function with unknown type input parameter, use Generics to give TypeScript the Type information it needs to determine the return type so code can be reused for all types throughout app
// example function
function clone(value) {
   let serialized = JSON.stringify(value);
   return JSON.parse(serialized);
}


// define generic type parameter name to inform TypeScript we will be referring to a generic type by the name of T (typically used by convention) in this function that we can now use throughout this method wherever a regular type, but the point being to tell TypeScript that the Output Type will always be the same as the Input Type. T is the type of the parameter that is passed in as a value.
function clone<T>(value: T): T {
   let serialized = JSON.stringify(value);
   return JSON.parse(serialized);

}

  • Example passing in a String, Number, and Object Literal, and Object Literal with a Named Property

clone('Hello')
clone(123)

var myObject: MyObject = {
   id: 1, 
   name: 'Luke'
}
clone(myObject)
clone({ name: 'Luke' })


Generics applied to Classes (to group generic methods that operator on the same types of objects)
  • Note: TypeScript considers built-in JS array type as a Generic Class, such that both these lines of code are equivalent
var arr1: number[] = [1, 2, 3]
var arr2: Array<number> = [1, 2, 3]
  • Note: Typed Key Value Pair Class with key and value properties and accepts two Generic Type Parameters 
class KVP<TKey, TValue> {
   constructor(
      public key: TKey,
      public value: TValue
   ) {
   }
}
// create instant of key value pair object using any two types desired. 
// Hover over in Visual Studio Code to see that TypeScript dynamically infers the generic type parameters based on values passed in
let kvp1 = new KVP<number, string>(1, 'Luke'); // TypeScript knows this is instance of type KVP number string
let kvp2 = new KVP<string, Date>('Second', new Date(Date.now())); // Explicitly defining to tell TypeScript the types we want by applying generic type syntax but instead using the actual types we want. TypeScript will remember these generic type parameters through codebase
let kvp3 = new KVP<number, string>(3, 'Third');
  • Define a Generic Class that interacts with any instances of Generic Key Value Pair type and iterates through a collection of any type of KVP objects and prints them to console. Pass in two generic type parameters T and U to the class 
class KVPPrinter<T, U> {
    // define constructor that accepts array of key value pairs that all share same types T and U
    constructor(private pairs: KVP<T, U>[]) {
    }
    print() {
        for (let p of this.pairs) {
            // statically typed access to objects key and value properties regardless of types T and U since we know they are all KVP objects
            console.log(`${p.key}: ${p.value}`)
        }
    }
}
// implement by creating instance and using it to print twp KVP objects, but only passing in pair one and three variablessince they share same KV types (TypeScript knows this printer object is an instance of KVPPrinter number, string (and only accepts pairs with generic parameters of number and string). If pair1 and pair2 were passed in then TypeScript would give error since they the pair of generic type parameters of each do not match.
var printer = new KVPPrinter([ pair1, pair3 ])


Modules
  • Benefits - mechanism that decoupling modularises clear component boundaries and encourages explicit dependency injection to avoid spaghetti code in global namespace
  • Relevant JS Design patterns, Modularisation Concepts, and Encapsulation Methods to encapsulate and organise codebases
  • Namespaces Approach (aka Internal Module Approach) (encapsulate and organise code using this modularisation concept)
    • TypeScript Namespace is simply syntactic sugar to create an IIFE (so all code written in namespace stays in the same scope without leak unless explicitly exposed to attach the type to the namespace object so accessible anywhere in the app, as even if within the same namespace but declared in different namespace declarations that are private by default)
    • Benefits - avoid naming collisions, refer to groups of types and single organisational units
    • Note: TypeScript Namespaces (like Interfaces) do not generate any additional code when compiled to JS
    • Note: The same Namespace may be used multiple times to assist in organising code, but when using Types outside of the Scope they are defined in you need to expose them first using the Export keyword
    • Alias using import keyword to use Type from another Namespace

// use periods to create a namespace hierarchy

namespace MyObjectApp.Model {
   export interface MyObject {
      id: number;
   }
}

namespace MyObjectApp.Model {
   export enum MyObjectAppState {
      New = 1,
      Complete
   }
}

namespace DataAccess {

   import Model = MyObjectApp.Model;
   import MyObject = Model.MyObject;

   export interface IMyObjectAppService {
      getAll(): MyObject[];
   }
}

    • Immediately Invoked Function Expression (IIFE) Design Pattern used for Encapsulation of code while it executes and choosing which parts of the code to expose by specifying them in the return value (to achieve truly Private Variables and Types)

var jQuery = {
   version: 2,
   fn: {} // object to attach custom functions to
};

// IIFE is an immediately invoked function wrapped in parenthesis
// pass an object into the function so the function can refer to variables or methods on the object or build upon it
(function defineType($) { // refer to jQuery as $ inside the IIFE
   if($.version < 1) { throw 'Error' }

   $.fn.myPlugin = function() {
      // 
   }
})(jQuery) // pass in jQuery variable when immediately invoked here


      • IIFE Design Pattern
        • IIFE Design Pattern is used in JS to describe Namespaces, Classes, and Enums
          • Note: Within Namespace code is Private by default unless explicitly exposed
// declaration of global variable for Top-Level Namespace
var TodoApp;

// TypeScript generates global object to hold Top-Level Namespace "TodoApp" variable in the IIFE Design Pattern form
(function (TodoApp) {
   var Model; // declaration of Nested Namespace "Model" but otherwise no logic in "TodoApp" namespace
   (function (Model) { // Model Namespace object
      (function (TodoState) { // TypeScript uses same IIFE Design Pattern to generate this Enum
         TodoState[TodoState["New"] = 1] = "New";
         TodoState[TodoState["Complete"] = 2] = "Complete";
      })(Model.TodoState || (Model.TodoState = {}));
      var TodoState = Model.TodoState; // Enum is exposed publicly (by using the TypeScript export keyword) by assigning it to a property of the Model Namespace object
   })(Model = TodoApp.Model || (TodoApp.Model = {}));
})(TodoApp || (TodoApp = {})); // IIFE invoked. Use variable passed in if available, otherwise assign TodoApp global variable to an empty object to ensure it is defined when passed into the IIFE function

Internal Module Approach - Example of Service with Encapsulated/Inaccessible Private Static Variable and Methods (inaccessible from other components in the app, but fully accessible to classes and functions defined within the same namespace declaration, use for hiding implementation details of generating new todo items for example)
  • Group Modules together controlling which components belong to which objects by wrapping them under umbrella namespaces to avoid the global scope
namespace DataAccess {

   import Model = TodoApp.Model;
   import Todo = Model.Todo;

   // variable moved outside of TodoService with 'private static' keywords removed and replaced with 'let'
   private static  let _lastId: number = 0;

   export interface ITodoAppService {
      getAll(): Todo[];
   }

   class TodoService implements ITodoService {
   
      // optionally move outside of service and make this getter method a regular function
      get generateTodoId() {
         // reference by name now since declared in same scope as class
         return TodoService._lastId += 1;
      }

      constructor(private todos: Todo[]) {
      }
...      
   }
}

External Module Approach - Used for browser-based lazy loading solutions in the RequireJS and NodeJS development (achieves separation of concerns)
  • Files themselves are used as the Module scope (rather than an IIFE within a file) (instead of keyword in brackets indicating scope of a module like in the Internal Module Approach). So it is pointless applying a internal Namespaces within External Modules, since External Modules are represented by the files themselves Export members using the export keyword
  • Add Configuration Parameter (Module Compiler Setting to indicate what the compiled output should be i.e. amd, commonjs, es2015, es5, system, umdto TypeScript configuration in tsconfig.ts file to inform TypeScript that we are using the External Module Approach and prevent errors when using export keyword in the file
  • Choose the Module Configuration Setting depending on which Module Loader we are using (i.e. System.js, which attempts to implement the proposed ECMAScript specification) which is used to load module files and manage module imports at runtime. Use System.js as temporary fix until native browser support is available
  • Index.html must be updated to change configuration for loading app
  • Storead and export the enum and interface in Model.ts and import into modules that depend on them
  • Module import of Module loaders allow use of full URL so make a library available in a module to make Module loader aware that the module expects the library to be loaded and available before the module is loaded and other modules that depend on the module containing that import (instead of in script tag in index.html) (i.e. import '//code.jquery.com/jquery-1.12.1.min.js'). Noting that after installing the Type Declaration TypeScript already knows about all the jQuery types (not because of the import)
  • When exporting members from a module, one member may be chosen as the Default Member 
    • Expose as Default Member exported from a module export default class Todo { ...
    • Replace imported default members with just their name (i.e. import Todo, { TodoState } from './Model')
tsconfig.ts
{
  "compilerOptions": {
      "target": "es5",
      "module": "system" // compiled output and allows us to write ECMAScript 2015 syntax now
   }
}

index.html
.
<head>
   <link rel="stylesheet" href="//   ... bootstrap cdn  ...>
</head>
<body>
  <!-- include module scripts explicitly -->
  <script type="text/javascript" src="model.js"></script>
  <script type="text/javascript" src="DataAccess.js"></script>
  <script type="text/javascript" src="app.js"></script>
<!-- script tag for Module Loader and use Module Loader API to dynamically load and execute application module. replace all script tags with this single reference to the Module Loader (System.js). Alternatively use NPM to install the System.js package with 'npm install systemjs' command -->
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.22/system.js"></script>
<script type="text/javascript">
   // temporary System.js configuration statement
   System.defaultJSExtensions = true;
   // use System.js return a module in a promise
   System.import(TodoApp').then(function(module) {
   
      new module.TodoApp(document, [
         'xyz',
         'abc'
      ]);

// code that loads the Todo service from the DataAccess Module and initialises it with a todo and prints all the todos to console
   
</script>
</body>
  • All modules defined within the file are private by default and only accessible when exported, and components defined outside the module must be imported
  • Two Approaches to Import Modules (both supported by TypeScript and both compiled to the same JS code)
    • Require (like used by NodeJS) syntax (initial TypeScript supported syntax)
DataAccess.ts

// Require Syntax to import modules to import Todo type into the scope of this module, achieved by referencing the Model module (which is in a separate file/scope)
// extension of the file path is not required
import Model = require('./model');

// reference components in this module either through object Model itself (by may be necessary to repeat code and not DRY), or by establishing an Alias shortcut to them as follows.
// Note: Conversely, the ECMAScript 2015 syntax allows importing of individual types and creation of Alias'
import Model = require('./model'); // require syntax to import components from external modules (in external files)
import Todo = Model.Todo; // require syntax with Alias

let _lastId: number = 0;

get generateTodoId() {
   return _lastId += 1;
}

export interface ITodoAppService {
   getAll(): Todo[];
}

class TodoService implements ITodoService {
   constructor(private: todos: Todo[]) {
   }
...
    • ECMAScript 2015 standard (PREFERRED) syntax (eventually to offer native browser support)
      • Module Loader (many different ones available such as System.js, etc. TypeScript generates code compatible with most of them) should be used to load external modules and manage module imports at runtime since importing external modules syntax is not widely supported
      • Reasons why necessary: 
        • Not widely supported yet across browsers
        • No standard implementation for loading the modules yet once referenced 
        • ECMAScript Loader Specification is in the process of being approved
   
app.ts

// ECMAScript 2015 syntax to import modules import all modules and orchestrate them to build app (using module file's relative path and name).
// ECMAScript 2015 allows defining Alias Model for use in this module (using the as syntax) within the import statement itself
import * as Model from './model'; // import everything exported from model.ts module and attach it to temp object named Model so they may be referred to in this module
import Todo = Model.Todo;
let todo: Todo;

// specifically import the Todo type that we are after instead of using an Alias
// give the type an Alias for when working with it in this module, then use the as syntax

import { Todo as TodoTask, TodoState } from './model';


let todo: TodoTask; // using Alias 

let service = new TodoService([]);

service.add({
  id: 1,
  name: 'Luke',
  state: TodoState.New
});

let todos = service.getAll();

todos.forEach(todo =>
   console.log(`${todo.name} [${TodoState[todo.state]}]`)
)

    • No Import (it is not mandatory to import anything at all from a module, in which case use this syntax, only really used for scripts that we depend on loading another module to modify the environment somehow)
import './model'

Consume JS Library without TypeScript Information - .
  • TypeScript Declaration Files file that describes the type information of a JS library implementation that is not written in TypeScript. TypeScript generates these TypeScript Declaration Files by setting the Declaration Compiler option to true, and compile the declaration file (with .d.ts extension) with tsc -w . TypeScript creates Ambient Declarations by attaching the declare keyword in front of any type that represents actual code (i.e. Enums, Variables, Classes) but do not define an implementation, indicating that the type definition describes an implementation from somewhere else.
  • Usage - not really just for a SPA, but when creating a Utility Library of code to share with a team or application, you could bundle the minified JS along with the TypeScript Definition so they have the JS to run and TS for the type information.
tsconfig.ts
{
  "compilerOptions": {
      "target": "es5",
      "declaration": "true"
   }
}

TodoServices.d.ts (generated file)
interface Todo {
...
}
declare enum TodoState {
   Active = 1,
   Complete = 2,
}
interface ITodoService {
...
}
declare class TodoServices implements ITodoService {
...
}

Typings (deprecated TSD Tool) supported by "Definitely Typed" community on GitHub to Download TypeScript Declarations for most Open Source Libraries (helps overcoming the issue that not all libraries are written in TypeScript)
  • Note: Many JS libraries are including TS Declaration Files in their distribution packages (with the type information)
  • Install TSD Package (now known as Typings)
npm install -g tsd


  • Typings (still maturing) replaces deprecated TSD Package (since March 2016) - promises to install all of the type definitions provided by TSD Tool and more
  • Search for Packages using TSD Command (i.e. type information for jQuery). - when it finds a result we can install the package with the install command (to download all the type info for jquery (i.e. jquery/jquery.d.ts) and save in a folder called Typings, containig the Ambient Declaration for the full jquery library with comments (this will provide full autocomplete and intellisense info for this type declaration i.e. declare var $: any; )
  • Visual Studio Code without errors means TSC Compiler has no errors
tsd query jquery
tsd install jquery --save
  • After downloading the type information that a project depends on, we want to save a reference to the definition in source control (not the definition itself), by attaching the --save flag, this will create a file called tsd.json containing a reference to the type definition files that were installed (allowing us to exclude the Typings folder from source control and when we get the project we just need to run the tsd install command to restore all the type definitions saved in the tsd.json file)
Source Maps for In-Browser TypeScript Debugging


  • Run app and Enable Breakpoint on exception
  • Chrome takes developer to line in JS where it thinks the error is if one arises (but it may be caused by a different line of code). However we coded in TypeScript so we want to know the TS line error
  • Source Maps allow language compilers where a variable or expression lives in the source code regardless or what the source code is
  • Opening a page in the debugger causes the browser to load the source file and read any metadata and upon error take the developer to the associated line of code
  • Native debugging support in the browser when source maps are provided for the JS emitted by TS
  • Add sourceMap: true to the tsconfig.json and then run tsc for TypeScript to generate Source Maps .js.map format (containing metadata telling the browser what JS expressions map to certain line of TS). With this setup and an error occurs, the browser will take us to the error line in the TS file. Also, full browser debugging is enabled so can use the browser console to execute expressions. The browser knows which Source Map file to load because of the last line of metadata in the source map and js files pointing the browser to the source map file and js file respectively that is downloaded and parsed by the browser and JS code mapped when the browser loads the js file

tsconfig.ts
{
  "compilerOptions": {
      "target": "es5",
      "declaration": "true",
      "sourceMap": true
   }
}

Decorators in ECMAScript 2015




  • Proposed syntax for implementing the Decorator Design Pattern to modify the behaviour of a class, method, property or parameter in a declarative way, defining common behaviour in a central location and apply it across the app for DRY, readable, and maintainable codebase
  • Example: Log each time a method is called and finished execution.
    • Imperative Inline Code: console.log(`add(${JSON.stringify(input)}) => ${JSON.stringify(todo)}`)
    • Decorator Design Pattern: Wrap the original method in another method to add the logging logic without having to change the code inside the add method. Basic Decorator Design Pattern:
var originalMethod = TodoService.prototype.add;

// method wrapping another method allowing attachment of behaviour to the method without changing the logic of the original method. replace original method with another method duplicating the behaviour of the original method (by calling the original method and returning the result)
TodoService.prototype.add = function(...args) {

   // additional logic (BEFORE) may be added before (at the entry into the call) and after the original method is called, but before return the value that the original value returned 
   console.log(`add(${JSON.stringify(args)})`);

   let returnValue = originalMethod.apply(this, args); // apply method accepts a single array or args. 'this' refers to the current calling object

    // additional logic (AFTER)
   console.log(`add(${JSON.stringify(input)}) => ${JSON.stringify(todo)}`);

   return returnValue;
}
    • Decorators
      • Functions with special signature that support targeting classes, methods, properties and parameters. Note that the Decorator Definitions ships with TypeScript in resources/app/extensions/typescript/server/lib

    • Decorators applied to Methods, Classes, Properties
      • Prefix member to decorate with @ followed by Decorator Name to attach behaviour defined in the decorator dynamically (even after it hs been defined)
@log
add(input: Todo {
   var x: ClassDecorator // create a variable using a Class Name from the Decorator Definition (i.e. declare type ClassDecorator)
...

TodoService.prototype.add = function(...args) {
...
}
// implementation of Decorator Method

  • target parameter is the object where the member is located, which in this situation is an instance of a Todo service
  • methodName parameter is the name of the method to be decorated
  • descriptor is an object containing all metadata for method we want to modify. It has numerous properties describing the member being decorated (i.e. configurable, enumerable, get, set, value, or writable). Noting that the value property is the method itself (i.e. the 'originalMethod' declared earlier), so we can move that into the log decorator function and modify it (changing TodoService.add to descriptor.value)
  • Change hard-coded method name to make the decorator say the name of the method that is being wrapped
  • Set a compiler flag to opt into Decorator Functionality and compile properly without error
  • Other Types of Decorators Developers can in addition to Decorating Methods they can define variations of it to properties, accessors, classes, and method parameters

function log(target:Object, methodName: string, descriptor: TypedPropertyDescriptor<Function>) { 
   var originalMethod = TodoService.prototype.add;
   descriptor.value TodoService.prototype.add = function(...args) {
      console.log(`${methodName}add(${JSON.stringify(args)})`);
      let returnValue = originalMethod.apply(this, args);
      console.log(`add(${JSON.stringify(input)}) => ${JSON.stringify(todo)}`);
      return returnValue;
   }
}

tsconfig.ts
{
  "compilerOptions": {
      "target": "es5",
      "declaration": "true",
      "sourceMap": true,
      "experimentalDecorators": false
   }

}


    • Director Factory 
      • Director Factory is a function. Inside this function a decorator function is returned, which has access to any parameter passed into the director factory. Allows us to pass parameters to a Decorator, as the signature of decorators themselves only allow the parameter to be what is to be decorated
      • Usage: Centralise all cross-cutting concerns of an app (i.e. logging, validation)

Links