TypeScript - ttulka/programming GitHub Wiki

  • TypeScript is both a language and a set of tools to generate JavaScript.
  • TypeScript compiler has a parameter that can switch between different versions of the ECMAScript standard.
  • Definition files for bringing typing into popular JavaScript libraries.

Benefits:

  • Compiling.
  • Strong or static typing.
  • Encapsulation.
  • Private and public member variable decorators.
class CountClass {
  private _count: number;
  constructor() {
    this._count = 0;
  }
  countUp() {
    this._count ++;
  }
  get count() {
    return this._count;
  }
}
var countInstance = new CountClass() ;
countInstance.countUp();
countInstance.countUp();
console.log(countInstance.count);   // 2

Typing

function doCalculation(
  a : number,
  b : number,
  c : number) {
  return ( a * b ) + c;
}
var result = doCalculation(3,2,1);

var arrayOfNumbers: number [] = [1,2,3];
arrayOfNumbers = [3,4,5,6,7,8,9];

Inferred typing

TypeScript will infer the type of a variable based on its first usage, and then assume the same type for this variable in the rest of your code block.

var inferredString = "this is a string";
var inferredNumber = 1;
inferredString = inferredNumber;   // error TS2011: Build: Cannot convert 'string' to 'number'

var arrayOfNumbers = [1,2,3];
arrayOfNumbers = [3,4,5,6,7,8,9];
arrayOfNumbers = ["1", "2", "3"];  // error TS2322: Type 'string[]' is not assignable to type 'number[]'
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;

y = x; // OK
x = y; // error TS2322: Type '(b: number, s: string) => number' is not assignable to type '(a: number) => number'.
class Animal {
  feet: number;
  constructor(name: string, numFeet: number) { }
}
class Size {
  feet: number;
  constructor(numFeet: number) { }
}
let a: Animal;
let s: Size;

a = s;  // OK
s = a;  // OK
  • The second assignment is an error, because y has a required second parameter that x does not have, so the assignment is disallowed.

Duck typing

If it looks like a duck, and quacks like a duck, then it probably is a duck.

  • Inferred typing for more complex types.
var complexType = { name: "myName", id: 1 };
complexType = { id: 2, name: "anotherName" };
complexType = { id: "string" };  // error TS2322: Type '{ id: string; }' is not assignable to type '{ name: string; id: number; }'

Function return types

function addNumbers(a: number, b: number) : string {
  return a + b;
}
var addResult = addNumbers(2,3); // error TS2322: Type 'number' is not assignable to type 'string'
// fixed
function addNumbers(a: number, b: number) : string {
  return (a + b).toString();
}

Function signatures

function doSomethingWithACallback(
  initialText: string,
  callback : (initialText: string) => string
) {
  console.log('callback result: ', callback(initialText));
}

doSomethingWithACallback("abc", (text) => text.toUpperCase()); // callback result:  ABC 

The any type

Specifying that an object has a type of any, in essence, relaxes the compiler's strict type checking.

let list: any[] = [1, true, "free"];
list[1] = "test";  // okay
  • Don’t use the return type any for callbacks whose value will be ignored (use void instead).

The void type

Specifying the absence of having any type at all.

function warnUser(): void {
  alert("This is my warning message");
}

Union types

var unionType : string | number;
unionType = 1;
unionType = "test";

function addWithUnion(
  arg1 : string | number,
  arg2 : string | number
) {
  return arg1 + arg2;   // error TS2365: Operator '+' cannot be applied to types 'string | number' and 'string | number'.
}

Type aliases

type StringOrNumber = string | number;

function addWithAlias(
  arg1 : StringOrNumber,
  arg2 : StringOrNumber
) {
  return arg1.toString() + arg2.toString();
}

type CallbackWithString = (string) => void;

function usingCallbackWithString(callback: CallbackWithString) {
  callback("this is a string");
}

Enums

enum DoorState {
  Open, Closed
}  

var doorStatus = DoorState.Open;
console.log(`doorStatus is: ${doorStatus}`);  // doorStatus is: 0

doorStatus = DoorState.Closed;
console.log(`doorStatus is: ${DoorState[doorStatus]}`); // doorStatus is: Closed

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

enum Status { Ready, Waiting };
let status = Status.Ready;
let num = 0;

status = num; // OKAY
num = status; // OKAY

Tuples

let x: [string, number];
x = ["hello", 10];

Functions

Function overloads

function add(a: string, b: string) : string;
function add(a: number, b:number) : number;
function add(a: any, b: any): any {
  return a + b;
}
console.log(add(1, 1));      // 2
console.log(add("1", "1"));  // 11
  • Sort overloads by putting the more general signatures after more specific signatures.
  • Don’t write several overloads that differ only in trailing parameters (use optional parameters ? instead).
  • Don’t write overloads that differ by type in only one argument position (use union types | instead).

Interfaces

interface IComplexType {
  id: number;
  name: string;
  print() : void;
}
var complexType : IComplexType = { 
  id: 1, 
  name: "test", 
  print(){ console.log(this.id + ": " + this.name); } 
};
interface IComplexType {
  print() : void;
}
class ComplexType implements IComplexType {
  id: number;
  name: string;     
  constructor(id: number, name: string) {
    this.id = id;
    this.name = name;
  }                  
  print(){ console.log(this.id + ": " + this.name); } 
}
var complexType : IComplexType = new ComplexType(1, "test");

Function interfaces

interface SearchFunc {
  (source: string, subString: string): boolean;
}
let mySearch: SearchFunc = (source, subString) => {
  return source.search(subString) > -1;
}

Interfaces extending classes

When an interface type extends a class type it inherits the members of the class but not their implementations.

class Point {
  x: number;
  y: number;
}
interface Point3d extends Point {
  z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};

Combinations

Add additional members to an interface with another interface declaration:

interface Foo {
  x: number;
}
// ... elsewhere ...
interface Foo {
  y: number;
}
let a: Foo = ...;
console.log(a.x + a.y); // OK

Assertion vs. Casting

The compiler will not protect you from forgetting to actually add the properties you promised:

interface Foo {
  bar: number;
  bas: string;
}
var foo = {} as Foo;

The compiler will provide autocomplete for properties of Foo:

interface Foo {
  bar: number;
  bas: string;
}
var foo1 = <Foo>{ };
var foo1: Foo = { };

Classes

Access modifiers (private, protected and public)

accessible on public protected private
class yes yes yes
class children yes yes no
class instances yes no no
  • public by default
class ClassWithPrivateProperty {
  private id: number;
  constructor(id : number) {
    this.id = id;
  }
}
let privateAccess = new ClassWithPrivateProperty(10);
privateAccess.id = 20; // error TS2341: Property 'id' is private and only accessible within class

Constructor access modifiers (shorthand)

  • This shorthand syntax is available only within the constructor function.
class ClassWithPrivateProperty {
  constructor(private id: number, public name: string) { }
}
let privateAccess = new ClassWithPrivateProperty(1, "test");
privateAccess.name = "test2"; // okay
privateAccess.id = 2;         // error TS2341: Property 'id' is private and only accessible within class 

Readonly properties

class ClassWithReadOnly {
  readonly name: string;
  constructor(name : string) {
    this.name = name;
  }
}
var readOnly = new ClassWithReadOnly("test");
readOnly.name = "test2"; // error TS2540: Cannot assign to 'name' because it is a constant or a read-only property

readonly vs const: Variables use const whereas properties use readonly.

Static properties

class StaticProperty {
  static count = 0;
  static updateCount() {
    StaticProperty.count ++;
  }
}
StaticProperty.updateCount();
console.log(StaticProperty.count);
class StaticProperty {
  public static readonly MAGIC_CONST : number = 42;
}
console.log(StaticProperty.MAGIC_CONST); // 42

Abstract classes

abstract class AbstractEmployee {
  public id: number;
  abstract getDetails(): string;
}
class NewEmployee extends AbstractEmployee {
  public name: string;
  getDetails(): string { return `id : ${this.id}, name : ${this.name}`; }
}
var employee: AbstractEmployee = new NewEmployee();

Inheritance

interface IBase {
  id: number;
}
interface IDerivedFromBase extends IBase {
  name: string;
}
class InterfaceInheritanceClass implements IDerivedFromBase {
  id: number;
  name: string;
}
class BaseClass implements IBase {
  id: number;
}
class DerivedFromBaseClass extends BaseClass implements IDerivedFromBase {
  name: string;
}

Structural typing

interface Named {
  name: string;
}
class Person {
  name: string;
}
let p: Named = new Person();  // OK, because of type compatibility

Namespaces

namespace MyNameSpace {
  class NotExported {
  }
  export class NameSpaceClass {
    id: number;
  }
}
var obj = new MyNameSpace.NameSpaceClass();
obj.id = 1;

Splitting across files

Validation.ts:

namespace Validation {
  export interface StringValidator {
    isAcceptable(s: string): boolean;  
  } }

LettersOnlyValidator.ts:

/// <reference path="Validation.ts" />
namespace Validation {
    const lettersRegexp = /^[A-Za-z]+$/;
    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        } } }

Generics

var list: Array<number> = [1, 2, 3];
class Concatenator<T> {
  concatenateArray(inputArray: Array<T>): string {
    let returnString = "";
    for (let i = 0; i < inputArray.length; i++) {
      if (i > 0) returnString += ",";
      returnString += inputArray[i].toString();
    }
    return returnString;
  }
}
var stringConcat = new Concatenator<string>();

var stringArray: string[] = ["first", "second", "third"];
var stringResult = stringConcat.concatenateArray(stringArray);
class FootballClubPrinter< T extends IFootballClub > implements IFootballClubPrinter< T > {
  // ...
}

Decorators (TypeScript Annotations)

  • Experimental feature of the TypeScript compiler.
  • Proposed as part of the ECMAScript 7 standard.
function SimpleDecorator(constructor : Function) {
  console.log('simpleDecorator called.');
}
@SimpleDecorator
class ClassWithSimpleDecorator {
}
function MethodDec(target: any, methodName: string, descriptor?: PropertyDescriptor) {
  console.log(`target: ${target}`);
  console.log(`methodName : ${methodName}`);
  console.log(`target[methodName] : ${target[methodName]}`);
}
class ClassWithMethodDec {
  @MethodDec
  print(output: string) {
    console.log(`ClassWithMethodDec.print` + `(${output}) called.`);
  }
}   

Declaration Files (.d.ts)

Allow to use external and third-party JavaScript libraries within TypeScript code.

  • Generate a declaration file for a TypeScript code: tsc test.ts -d
  • Install a declaration via npm: npm install @types/jquery

Global variables

globals.d.ts:

declare var MY_GLOBAL_VARS: string [];

test.d.ts:

///<reference path="globals.d.ts"/>

class GlobalLogger {
  static logGlobalsToConsole() {
    for(let global of MY_GLOBAL_VARS) {
      console.log(`found global var: ${global}`);
    }
  }
}
window.onload = () => {
  GlobalLogger.logGlobalsToConsole();
}

index.html:

<script src="test.js"></script>
<script>
  var MY_GLOBAL_VARS = ["abc", 123];
</script>

Function overrides

declare function trace(arg: string | number | boolean );
declare function trace(arg: { id: number; name: string });

Modules

  • Declare functions and classes as a single unit.
  • At a collision will be merged automatically by the compiler as if they were a single definition. ErrorHelper.d.ts:
declare module ErrorHelper {
  function containsErrors(response);
  function trace(message);
}

test.ts:

// ...
if (ErrorHelper.containsErrors(message))
  ErrorHelper.trace(message);

Nested namespaces

declare module FirstNamespace {
  module SecondNamespace {
    module ThirdNamespace {
      function log(msg: string);
    }
  }
}
// can be called as FirstNamespace.SecondNamespace.ThirdNamespace.log("test");

Modularization

Module is a separate TypeScript file that exposes classes, interfaces, or functions for reuse in other parts of the project. lib/Module1.ts:

export class Module1 {
  print() {
    print(`Module1.print()`);
  }
}
function print(functionName: string) {
  console.log(`print() called with ${functionName}`);
}
import {Module1} from './lib/Module1';

let mod1 = new Module1();
mod1.print();   // print() called with Module1.print()

Exporting variables

lib/Module1.ts:

var myVariable = "This is a variable.";
export { myVariable }
import { myVariable } from './lib/Module1';
console.log(myVariable);

Module renaming

import {Module1 as m1} from './lib/Module1';
let mod1 = new m1();
mod1.print();
export class Module1 {
  print() {
    print(`Module1.print()`);
  }
}
export {Module1 as NewModule};

Default exports

We can mark a single item as a default export. lib/Module2.ts:

export default class Module2Default {
  print() {
    console.log(`Module2Default.print()`);
  }
}
export class Module2NonDefault {
  print() {
    console.log(`Module2NonDefault.print()`);
  }
}
import M2 from './lib/Module2';

let m2default = new M2();
m2default.print();

Combinations

foo.d.ts:

export var SomeVar: { a: SomeType };
export interface SomeType {
  count: number;
}
import * as foo from './foo';

let x: foo.SomeType = foo.SomeVar.a;
console.log(x.count);

AMD module loading

  • Asynchronous Module Definition
npm install requirejs --save
npm install @types/requirejs --save

RequireConfig.js:

require.config( {
  baseUrl: "."
});

index.html:

<script type="text/javascript" 
  src="./node_modules/requirejs/require.js" data-main="./RequireConfig" >
</script>

tsconfig.json:

{ "compilerOptions": {
    "module": "amd",
    ...

lib/Module3.ts:

export class Module3 {
  print() { console.log(`Module3.print()`); }
}
var m3 = require('./Module3');

var module3 = new m3.Module3();
module3.print();

Templates

https://www.typescriptlang.org/docs/handbook/declaration-files/templates.html

Testing

TODO

References

⚠️ **GitHub.com Fallback** ⚠️