Generics in TypeScript

·

3 min read

Generics are like function arguments, but for types in class and function definitions in TypeScript.

Generics always allows us to define the type of a property/argument/ return type value at a future point. it is used heavily when writing re-usable code.

Let's take a sample example of Generic

Example


X class HoldNumber<TypeOfData>{
Y        data : TypeOfData;

Z       constructor(data : TypeOfData){
            this.data = data;
        }
}

I have marked line X, Y and Z specifically to point out certain things.

  1. Line X : See how we specified the TypeOfData between <>

  2. Line Y and Z : We declared the TypeOfData as the generic we passed

Due the above declaration, we can specify the data type for our class based on what we passed, if we make any error now; TS will show that to us. Like we are specifying the generic as a number but we are passing a string, we will see an error. Though this seems pretty much the normal functionality for TS, in large code bases it prevents many bugs.

const holdNumber = new HoldNumber<number>('Sid'); // will show an erro
const holdNumber2 = new HoldNumber<string>('Sid'); // won't show an error

On passing any number in holdNumber, we won't see any error.

const holdNumber = new HoldNumber<number>(108);

Generic Functions

First see an example of what will be considered as a generic function


function printString(arr : string[]) : void{
    arr.forEach(ele => console.log(ele));
}

function printNumber(arr : number[]) : void {
    arr.forEach(ele => console.log(ele));
}

// observe this function declaration
function printAnything<T>(arr : T[]) : void {
    arr.forEach(ele => console.log(ele));
}

See the third function declaration we did. Why we need that? Go through the first two function examples in this section.

We needed to print string and numbers but we cannot pass an 'any' as type that will be poorly written implementation. We can either declare function of different types and write again and again like we did with printString and printString or we can use the concept of 'Generics' here.

Generic constraints

Consider we are doing the following things

class CarSample{
    print(){
        console.log('I am car');
    }
}

class HouseSample{
    print(){
        console.log('I am a house');
    }
}

function printHouseOrCars<T>(arr : T[]) : void {
    arr.forEach(ele => ele.print());
}

This will show an error because TypeScript won't know if T has a print() method or not.

For this we will use an interface and will extend it to generic


class CarSample{
    print(){
        console.log('I am car');
    }
}

class HouseSample{
    print(){
        console.log('I am a house');
    }
}

// Implement interface here

interface Printable {
    print() : void
}

function printHouseOrCars<T extends Printable>(arr : T[]) : void {
    arr.forEach(ele => ele.print());
}

Our interface Printable has a print() method, now while defining the generic, we used the 'extend' keyword and told TS that we are putting an argument which will have a print() method in it.

Now if we call the function, we will have to make sure the argument we are passing has a print() method in it

printHouseOrCars([new CarSample(), new HouseSample()]); // won't throw an error
printHouseOrCars([1,2,3,4]) // will throw an error

The second example will throw an error because now we have added a constraint in our printHouseOrCars function that the array must have elements who have a print() method inside them.

The array [1,2,3,4] does not fit in this constraint hence the error.

Thankyou for reading.