Iterators and Generators

Iterators are basically objects that can be hold more than one item. Iterators are collections of objects or variables or items that can be iterated on to do some sort of processing on each item in the collection. Arrays are an exmaple of iterators.

In TypeScript, an object is demmed iterable if it has an implementation for Symbol.iterator property. Some built-in types like Array, Map, Set, String, Int32Array, etc. have their Symbol.iterator property already implemented.

Symbol.iterator function on an object is responsible for returning the list of values to iterate on.

for...of... statements

for...of... loops over an iterable object will invoke the Symbol.iterable property on the object.

1
2
3
4
5
let someArray = [1, 'string', false]

for (let entry of someArray) {
console.log(entry) // 1, 'string', false
}

Intersection Types

An intersection type combines multiple types into one. This is allows you to add together existing types to get a single type that has all the features you need.

Union Types

Union Types are closely related to intersection types, but they are used very differently. Occasionally, you’ll run into a library that expects a parameter to be either a number or a string.

Type Guards

Union Types are useful for modeling situations when values can overlap in the types they can take on.

TypeScript has what is called a type guard.

A type guard is some expression that performs a runtime check that guarantees the type in some scope. To define a type guard, we simply need to define a function whose return type is a type predicate.

1
2
3
function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined
}

pet is Fish is our type predicate in this example. A predicate takes the form parameterName is Type, where parameterName must be the name of a parameter from the current function signature.

Any time isFish is called with some variable, TypeScript will narrow that variable to that specific type if the original type is compatible.

1
2
3
4
5
if (isFish(pet)) {
pet.swim()
} else {
pet.fly()
}

Notice that TypeScript not only knows that pet is Fish in the if branch, it also knows that in the else branch you don’t have a fish, so you must have a Bird.

typeof type guards

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function isNumber(x: any): x is number {
return typeof x === 'number'
}
function isString(x: any): x is string {
return typeof x === 'string'
}

function padLeft(value: string, padding: string | number) {
if (isNumber(padding)) {
return Array(padding + 1).join(' ') + value
}
if (isString(padding)) {
return padding + value
}

throw new Error(`Expected string or number, got ${padding}`)
}

However, having to define a functino to figure out if a typeis a primitive is kind of a pain.

TypeScript will recognize typeof x === 'number' as a type guard on its own. This means we could just write checks inline.

1
2
3
4
5
6
7
8
9
10
11
function padLeft(value: string, padding: number | string) {
if (typeof padding === 'number') {
// ...
}

if (typeof padding === 'string') {
// ...
}

throw new Error(`Expected string or number, got ${padding}`)
}

These typeof type guards are recognized in two different forms: typeof v === 'typename' and typeof v !== 'typename'.

instanceof type guards

instanceof type guards are a way of narrowing types using their constructor function.

The right side of the instanceof needs to be a constructor function, and TypeScript narrow down to:

  • the type of the function’s prototype property if its type is not any.

  • the union of types returned by that type’s construct signatures.

in that order.

Generics

1
2
3
function identity<T>(arg: T): T {
return arg
}

We add a type variable T to the identity function. This T allows us to capture the type the user of the code provides, so that we can use that information later.

We say that this version of the identity function is generic, as it works over a range of types.

Once we’ve written the generic identity function, we can call it in one of two ways.

The first way is to pass all of the arguments, including the type argument, to the function.

1
let output = identity < string > 'myString' // type of output will be string

Here we explicitly set T to be string as one of the arguments to the function call, denoted using <> around the arguments rather than ().

The second way is also perphaps the most common. Here we use type argument inference – that is, we want the compiler to set the value of T for us automatically based on the type of the argument we pass in.

1
let output = identity('myString') // type of output will be 'string'

Working with Generic Type Variables

When you begin to use generics, you’ll notice that when you create generic functinos like identity, the compiler will enfore that you use any generically typed parameters in the body of the function correctly.

Generic Types

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function identity<T>(arg: T): T {
return arg
}

let myIdentity: <T>(arg: T) => T = identity

// we can also write the generic type as a call signature of an object literal type:

let myIdentity: { <T>(arg: T): T } = identity

interface GenericIdentityFn {
<T>(arg: T): T;
}

// or

interface GenericIdentityFn<T> {
(arg: T): T;
}

let myIdentity: GenericIdentityFn<number> = identity

Generic Classes

A generic class has a similar shape to a generic interface.

Generic classes have a generic type parameter list in angle brackets(<>) following the name of the class.

1
2
3
4
5
6
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>()

Generic classes are only generic over their instance side rather than their static side.

Decorator

A Decorator is a special kind of declaration that can be attached to a class declaration, method, accessor, property, or parameter.

Sometimes it is required to have additional features to support annotating or modifying classes and class members. Decorators provide a way to add both annotation and a meta-programming syntax for class declarations and members.

Decoration use the form @expression, where expression must evaluate to a function that will be called at runtime with information about the decorated declaration.