Advanced Typescript

Overview

  • Intersection and Union Types
  • Type Guards, Polymorphic Distypes
  • Decorators
  • Async Patterns
  • Linting

Advanced Basic Types

Destructing arrays and object

Destructuring
The process of assigning the elemenst of an arrrat or the props of and object to individual variables
Rest Parameter
Access the rest of an array in function params
let medals: string[] = ['gold', 'silver', 'bronze'];
let [first, second, third] = medals;
function Log2Foo([foo1, foo2, ...rest]: Foo[]) {
  console.log(foo1);
  console.log(foo2);
  console.log(rest);
}
function Log2Foo([foo1: one, foo2: two, ...rest]: Foo[]) {
  console.log(one);
  console.log(two);
  console.log(rest);
}
let person = { name: 'fred', adddress: '1 main st', phone: 555-1234 };
let { name, address, phone } = person;
console.log(name, address, phone);
let person = { name: 'fred', adddress: '1 main st', phone: 555-1234 };
let { name: personName, address: personAddress, phone: personPhone } = person;
console.log(personName, personAddress, personPhone);

Spead operator

Spead
apply element into new elements in an array or function parameters
let items: number[] = [1, 2];
let extraitems: number[] = [10, 20];
items.push(...extraItems);
console.log(items);  // [1, 2, 10, 20]
let items: number[] = [1, 2];
let extraitems: number[] = [10, 20, ..items];
items.push(...extraItems);
console.log(items);  // [10, 20, 1, 2]

Tuple Types

Tuple Type
combinces a set of numerically named properties with the members of an array type
  • Extension to arrays
  • Inital Types defined and must match
  • Additional element can be any defined type

#+NAME Creating and modifying Tuples

// 1.
let idValue: [number, string] = [10, 'stringValue'];
// 2.
idValue[0] = 1; // OK
idValue[0] = 'stringValue'; // Error
idValue[1] = 'stringValue'; // OK
idValue[1] = 1; // Error
// 3.
idValue[2] = 'stringValue'; // OK
idValue[2] = 1; // OK

#+NAME KeyValuePair tuple type

interface KeyValuePair<KeyType, ValueType> extends Array<KeyType | ValueType> {
  0: KeyType;
  1: ValueType;
}

Union Types

Union Types
Specifies several types for a value with |

#+NAME Union Type in function parameter

function LogId(id: string | number) {
  console.log(id + " is either a string or number");
}

Intersection Type

Intersection Types
Specify a value that will contain all members of several types with &

#+NAME Intersection Types as a return value

function FutureIsNow() : Clock & Radio {
  return new ClockRadio();
}

Mixins

Mixin
Classes whose functionality is added to another class

#+NAME Mixin

class Clock { void NovTwelve1955() {/*dostuff*/} }
class Radio { void kbillysSuperSoundsOfTheSeventies() {/*dostuff*/} }
class FutureDevice implements Clock, Radio {
  void NovTwelve1955() {/*empty*/}
  void kbillysSuperSoundsOfTheSeventies() {/*empty*/}
}
applyMixins(FutureDevice, [Clock, Radio]);

function applyMixins(derivedCtor: any, baseCtors: any[]) {
  // function pulled straight from docs http://www.typescriptlang.org/docs/handbook/mixins.html
  baseCtors.forEach(baseCtor => {
    Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
      derivedCtor.prototype[name] = baseCtor.prototype[name];
    });
  });
}

String Literal Type

String Literal Type
String literal treated as a distinct type

#+NAME String Literal Type

let strLitType: 'Foo';
strLitType = 'Foo'; // OK
strLitType = 'Baz'; // Error

#+NAME String Literal Type as Enum

let strLitType: 'Foo' | 'Bar';
strLitType = 'Foo'; // OK
strLitType = 'Bar'; // OK
strLitType = 'Baz'; // Error

Type Alias

Type Alias
refer to a type by a different name

#+NAME Type Alias for a String Literal Type

type FooBar =  'Foo' | 'Bar';
let foo: ForBar = 'Foo'; // OK
let bar: ForBar = 'Bar'; // OK
let baz: ForBar = 'Baz'; // Error

Advanced Types

Polymorphic this types

Polymorphic this types
A polymorphic this types represents a type that is the subtype of the containing class of interface
  • used heavily in Fluent builders

#+NAME Polymorphic this type when class is extended

class Vehicle { Drive() => this; }
class Truck extends Vehicle{};
class Car extends Vehicle{};
// different types returned
Truck.Drive(); // returns typeof(Truck)
Car.Drive(); // returns typeof(Car)

Declaration Merging

Declaration Merging
The complier merges two separate declarations decllared with the same name into a single definition
allowed
interfaces, enums, namspaces, namespaces with classes, namespaces with functions, namespaces with enums
dissallowed
classes with classes

#+NAME Model Merge by declaration merging

interface Power { powerSource: string; }
interface Power { owner: string: }
let foo: Power;
foo.powerSource = 'shazam';
foo.owner = 'billy';

#+NAME Module Augentation by declaration merging

// classes.cs
inteface IFoo {
  bar();
}
export class Foo extends IFoo
{
   bar() {}
}
// FooExtension.cs
import { Foo } from './classes';
declare module './classes' {
  interface IFoo {
    baz(): void;
  }
}
Foo.prototype.baz = function() {
  // new  method added to Foo;
}

Type Guards

typeof
Check string | number | boolean | symbol

#+NAME typeof Type Guard

let x: string | number = 11;
if (typeof x === 'string') {
  // string type enforced
}
instanceof
check union types

#+NAME instance of Type Guard

let device: Clock | Radio;
if (device instanceof Clock) {
  // Clock type enforced
}
type predicate
check interfaces

#+NAME type predicate Type Guard

interface Foo { bar: string }
function isFoo(x: any): x is Foo {
  return (<Foo>x).bar !== undefined;
}

Symbols

  • Requires tsconfig.js::{compilerOptions{target: 'es2015'}}
  • Characteristics
    • Primitive Data Type
    • Unique
    • Immutable
  • Use Cases
    • Unique Constraints
    • Computed Property Declarations
    • Customize Internal Language Behaviour, there are a few well know symbols (hasInstance…)

#+NAME Symbol Initalization

let foo = Symbol('description');
let bar = Symbol('description');
console.log(foo === bar);  // false
console.log(typeof foo);  // 'symbol'

#+NAME Symbol as property key

let fooSymbol = Symbol('description');
let bazObject = {
  [foo]: 'value for my symbol key'
}
console.log(bazObject[fooSymbol]);  // Value for my symbol key

#+NAME Symbol for a Computed Property Key

// classes.ts
let FOO_SYMBOL = Symbol();
export class BazClass {
  [FOO_SYMBOL]() {
    console.log('Some Message');
  }
}
// usage.cs
import {FOO_SYMBOL, BazClass} from './classes.ts';
let baz = new BazClass();
baz[FOO_SYMBOL]();  // 'Some message'

#+NAME Symbol to modify internal manguage behaviours by overriding Symbol.hasInstance

// classes.tse
export class BazClass {
  static [Symbol.hasInstance](obj: Object) {
    // do different check
  }
}

Decorators

Class Decorators

#+NAME Class Decorator

function classDecorator(classCtor: Function) {
  /*do stuff*/
}
@classDecorator
class FooClass {
  barMethod(){}
}

#+NAME Replace a constructor with a Class Decorator

function classDecorator<TFunction extends Function>(classCtor: TFunction): TFunction  {
  let newCtor: Function = function() {
    console.log('Live from new ctor...');
  }
  newCtor.prototype = Object.create(classCtor.prototype);
  newCtor.prototype.constructor = classCtor;
  return <TFunction>newCtor;
}

Decorator Factory

Decorator Factories
Allow specifying additonal parameters

#+NAME Class Decorator Factory

function classDecoratorFactory(element:string) {
  return function(classCtor: Function): void  {
    console.log(element);
  }
}
@classDecoratorFactory('elementValue')
class FooClass {}

Property Decorators

#+NAME Property Decorator

function propertyDecorator(classCtorOrPrototype: Function, propertyKey: string)  {
    /*do stuff*/
}
class FooClass {
  @propertyDecorator
  barProperty: string;
}

Parameter Decorators

#+NAME Parameter Decorator

function propertyDecorator(classCtorOrPrototype: Function, propertyKey: string, parameterIndex: number)  {
    /*do stuff*/
}
class FooClass {
  function barMethod(@parameterDecorator baz:string){}
}

Metho

Method and Accessor Decorators

#+NAME Method Decorator

function methodDecorator(staticMethodOrPrototypeMethod: any, nameOfDecoratedMember: string, description: ProperyDescriptor) {
  /*do stuff*/
}
class FooClass {
  @methodDecorator
  barMethod(){}
}
Property Descriptors
Object that describes a property and how it can be manipulated

#+NAME PropertyDescriptor interface

interface PropertyDescriptor {
  configurable?: boolean;
  enumerable?: boolean;
  value?: any;  // the value assigned (eg, the function for class methods)
  writable?: boolean;  // is it readonly?
  get? (): any;
  set?(v: any);
}

#+NAME creating a readonly Method with Method Decorator and PropertyDescriptor

function readonly(target: any, propertyKey: string, descriptor: ProperyDescriptor) {
  console.log(`Setting ${propertyKey} to readonly.`);
  descriptor.writable = false;
}
class FooClass {
  @readonly
  barMethod(){}
}

Asyncronous Methods

Callbacks

#+NAME Callback Flow

interface FooCallback (err: Error, data: string[]): void;
function FooMakesCallback(callback: FooCallback) {
let data = fetchData();
  if (data.length > 0) {
    callback(null, data);
  } else {
    callback(()=>{console.log('there was an error')}, null);
  }
}

Promises

#+NAME Promises flow

let p: Promise<TypeOfSuccess> = new Promise((resolve, reject) => {
  // ...perform async work
  if (success) {
    resolve(data);
  } else{
    reject(reason);
  }
});
p.then(successResult => console.log('success' + successResult);
p.catch(reason => console.log('rejected' + reason);

Async / Await

#+NAME Async/Await Flow

async function doAsyncWork() {
  let result = await SlowFunction();
  console.log(result);
}

#+NAME Async/Await Error handling using the promise returned

async function doAsyncWork() {
  let result = await SlowFunction();
  console.log(result);
}
doAsyncWork.catch(reason => console.log(reason));