Basic Types

Boolean, Number, String, Array, Tuple, Enum, Any, Void, Null, Undefined, Never

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Boolean
let isDone: boolean: false

// Number
let decimal: number = 6

// String
let color: string = 'red'

// Array
let list: number[] = [1, 2, 3]
let list: Array<number> = [1, 2, 3]

// Tuple
let x: [string, number] = ['hello', 10]

// Enum
enum Color {Red, Green, Blue}
let c: Color = Color.Green // 1

// Any
let notSure = 5
notSure = 'not sure'

// Void
function warnUser (): void {
alert('This is my warning message')
}

// Never
function error(message: string): never {
throw new Error(message)
}

Note:

By default null and undefined are subtypes of all other types, that means you can assign null and undefined to something like number

However, when using --strictNullChecks flag, null and undefined are only assignable to void and their respective types. This helps avoid many common errors. In cases where you want to pass in either a string or null or undefined, you can use the union type string | null | undefined.

Type assertions

1
2
3
let someValue = 'this is a string'
let strLength: number = (<string>someValue).length
let strLength: number = (someValue as string).length

Variable Declarations

Interfaces

One of TypeScript’s core principles is that type-checking focuses on the shape that value have. This is sometimes called ‘duck typing’ or ‘structural subtyping’. In TypeScript, interface fill the role of naming these types and are a powerful way of defining contracts within your code as well as contracts with code outside your project.

1
2
3
4
5
6
7
8
function printLabel(labelObj: { label: string }) {
}
// Or
interface LabelledValue {
label: string;
}
function printLabel(labelObj: LabelledValue) {
}

Optional Properties

1
2
3
4
interface SquareConfig {
color?: string;
width?: number;
}

Readonly Properties

1
2
3
4
5
6
interface Point {
readonly x: number;
readonly y: number;
}

let ro: ReadonlyArray<number>

Note: Readonly Array

1
2
3
4
5
6
let a: number[] = [1, 2, 3]
let ro: ReadonlyArray<number> = a
ro[0] = 12 // error
ro.push(5) // error
ro.legnth = 100 // error
a = ro // error

You can’t assign the entire ReadonlyArray back to a normal array. Instead, you could do this:

1
a = ro as number[]

Object literals get special treatment and undergo excess property checking when assigning them to other variables, or passing them as arguments. If an object literal has any properties that the target type doesn’t have, you’ll get an error.

Adding Index Signature

Add a string index signature if you’re sure that the object can have some extra properties that are used in some special way.

1
2
3
4
5
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}

Function Types

To describe a function type with an interface, we give the interface a call signature. This is like a function declaration with only the parameter list and return type given. Each parameter in the parameter list requires both name and type.

1
2
3
4
5
6
7
8
9
10
interface SearchFunc {
(source: string, subString: string): boolean;
}

let mySearch: SearchFunc

mySearch = function (source: string, subString: string) {
let result = source.search(subString)
return result > -1
}

Indexable Types

Similarly to how we can use interfaces to describe function types, we can also describe types that we can ‘index into’ like a[10] or ageMap['daniel']. Indexable types have an index signature that describes the types we can use to index into the object, along with the corresponding return types when indexing.

1
2
3
4
5
6
7
8
9
10
11
12
interface StringArray {
[index: number]: string;
}

let myArray: StringArray
myArray = ['Bob', 'Fred']
let myStr: string = myArray[0]

// readonly
interface ReadonlyStringArray {
readonly [index: number]: string
}

Class Types

Implementing an interface.

One of the most common uses of interfaces is explicitly enforcing a class meets a particular contract.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
inerface ClockInterface {
currentTime: Date;
setTime(d: Date);
}

class Clock implements ClockInterface {
currentTime: Date
setTime(d: Date) {
this.currentTime = d
}
constructor (h: number, m: number) {

}
}

Interface describe the public side of the class, rather than both the public and private side. This prohibits you from using them to check that a class also have particular types for the private side of the class instance.

Difference between the static and instance side of classes.

When working with classes and interfaces, it helps to keep in mind that a class have two types: the type of static side and the type of the instance side.

When a class implements an interface, only the instance side of the class is checked.

Notice: constructor sits in the static side, it is not included in this check.

Extending Interfaces

1
2
3
4
5
6
7
8
9
10
11
12
interface Shape {
color: string;
}

interface Square extends Shape {
sideLength: number;
}

let square = <Square>{}

square.color = 'blue'
square.sideLength = 10

An interface can extend multiple interface, creating a combination of all of the interfaces.

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Shape {
color: string;
}

interface PenStroke {
penWidth: number;
}

interface Square extends Shape, PenStroke {
sideLength: number;
}

let square = <Square>{}

Hybrid Types

1
2
3
4
5
6
7
8
9
10
11
12
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}

function getCounter(): Counter {
let counter = <Counter>function (start: number)
counter.interval = 123;
counter.reset = function () {}
return counter
}

Interface Extending Classes

When an interface type extends a class type it inherits members of the class but not their implementations. It is as if the interface has declared all of the members of the class without providing an implementation. Interface inherits even the private and protected members of a base class. This means that when you create an interface that extends a class with private or protected members, that interface type can only be implemented by that class or a subclass of it.

1
2
3
4
5
6
7
8
9
10
11
class Control {
private state: any;
}

interface SelectableControl extends Control {
select(): void
}

class Button extends Control {
select() {}
}

Classes

Example:

1
2
3
4
5
6
7
8
9
10
11
class Greeter {
greeting: string;
constructor (message: string) {
this.greeting = message;
}
greet() {
return 'Hello, ' + this.greeting;
}
}

let greeter = new Greeter('world')

Class Inheritance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Animal {
name: string;
constructor(theName: string) {
this.name = theName
}
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}`)
}
}

class Snake extends Animal {
constructor (name: string) {
super(name)
}

move(distanceInMeters = 5) {
console.log('Slithering...')
super.move(distanceInMeters)
}
}

class Hores extends Animal {
constructor (name: string) {
super(name)
}

move(distanceInMeters = 45) {
console.log('Galloping...')
super.move(distanceInMeters)
}
}

Public, Private, Protected Modifiers

Public by default

Private: when a member is marked private, it cannot be accessed from outside of its containing class.

Protected: The protected modifier acts much like the private modifier with exception that members declared protected can also be accessed by instance of deriving classes. Namely the protected property can be used in instance method.

Readonly Modifier

Readonly properties must be initialized at their declaration or in the constructor.

Accessors

TypeScript supports getters/setters as a way of intercepting accesses to a member of an object.

1
2
3
4
5
6
7
8
9
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
this._fullName = newName;
}
}

Static Properties

1
2
3
4
5
6
7
8
9
10
11
class Grid {
static origin = {x: 0, y: 0}
calculateDistanceFromOrigin(point: {x: number, y: number}) {
let xDist = (point.x - Grid.origin.x)
let yDist = (point.y - Grid.origin.y)
retrun Math.sqrt(xDist * xDist + yDist * yDist) / this.scale
}
constructor (public scale: number) {
// ...
}
}

Abstract Classes

Abstract classes are base classes from which other classes may be derived. They may not be instantiated directly.

Unlike an interface, an abstract class may contain implementation detail for its members. The abstract keyword is used to define abstract classes as well as abstract methods within an abstract class.

1
2
3
4
5
6
abstract class Animal {
abstract makeSound: void;
move(): void {
console.log('roaming the earth...')
}
}

Methods within an abstract class that are marked as abstract do not contain an implementation and must be implemented in derived classes.

Abstract methods share a similar syntax to interface methods. Both define the signature of a method without including a method body.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
abstract class Department {
constructor (public name: string) {
}

printName (): void {
}
abstract printMeetng () :void // must be implemented in derived classes
}

class AccountDepartment extends Department {
constructor (){
super('Accounting and Auditing')
}
printMeetng (): void {
// ...
}
generateReports () : void {
// ...
}
}

Advanced Techniques

Constructor Functions

When you declare a class in TypeScript, you are actually creating multiple declarations at the same time. The first is the type of the instance of the class.

1
2
3
4
5
6
7
8
9
10
11
12
class Greeter {
greeting: string;
constructor (message: string) {
this.greeting = message;
}
greet () {
return 'Hello, ' + this.greeting;
}
}

let greeter: Greeter;
greeter = new Greeter('world')

Here when we say let greeter: Greeter, we’re using Greeter as the type of instance of the class Greeter. Almost we use the class constructor as a type.

Using a class as an interface

A class declaration creates two things: a type representing instances of the class, and a constructor function.

1
2
3
4
5
6
7
8
class Point {
x: number;
y: number;
}

interface Point3d extends Point {
z: number;
}