Generics

Generics are necessary when we want to describe a relationship between two or more types (eg., a function argument and return type).

TS Docs

Functions with generic types

Consider

function identity(arg: any): any {
return arg;
}
let output = identity("myString"); // type of output will be 'any'
let output2 = identity(100); // type of output2 will be 'any'

Functions with generic types : types get bind at later stages (So types are parameterized)

function identity<T>(arg: T): T {
return arg;
}
let output = identity<string>("myString"); // type of output will be 'string'
let output2 = identity<number>(100); // type of output2 will be 'number'

Note: T is just a variable, it can be any name

In simple cases, type inference can work, compiler can read parameter value & set it's type to T.

function identity<T>(arg: T): T {
return arg;
}
let output = identity("myString"); // no explicit <string>
let output2 = identity(100);

Note: multiple generic types params can be passed

Interfaces with generic types

consider

interface Record{
id:any;
}
let a:Record = {
id:2
}
a.id //is any
interface Record<T>{
id:T;
}
let a:Record<number> = {
id:2
}
a.id //is number

Taking default generic type

interface Record<T = any>{
id:T;
}
let me:Record = { //no type passed
id:2
}

Describing function signatures

Describing function signatures with generic types

Earlier

interface myEventHandler {
(eventObj: any, message: string): string;
}
const evtButtonClick: myEventHandler = (_contact, _message) => {
};
/*now no need to specify types for function params as
/they are inherited from function signatures type*/
interface myEventHandler<T> {
(eventObj: any, message: T): T;
}
const evtButtonClick: myEventHandler<string> = (_contact, _message) => {
};
/*now no need to specify types for function params
as they are inherited from function signatures type*/

Classes with generic types

class GenericNumber < T, U > {
name: U | null;
constructor(myName: U) {
this.name = myName;
}
doSomething(x: T, y: T) {
};
}
let myGenericNumber = new GenericNumber <number,string>('hi');
myGenericNumber.doSomething(2, 3);

Generic Constraints

Generic Constraints while using generic types

Sample Issue

function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}

Context fix if input param is array

function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length);
return arg;
}

Extending generic types

interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
// Now we know it has a .length property, so no more error
return arg;
}
loggingIdentity(3);
// Error, number doesn't have a .length property
loggingIdentity({length: 10, value: 3});
loggingIdentity([1,2,3]]);

Others

Type parameters are associated with scopes, just like function arguments

function startTuple<T>(a: T) {
return function finishTuple<U>(b: U) {
return [a, b] as [T, U];
};
}
const myTuple = startTuple(["first"])(42);