Skip to main content

Command Palette

Search for a command to run...

Generics In Typescript

distributed Typescript...

Updated
7 min read
Generics In Typescript

Disclaimer: Having an understanding of essential Typescript is necessary to move ahead with Generics in typescript. You can play around the code on online typescript play.

If you have ever used Typescript for any project, then you must have heard or worked on any type in Typescript which we explicitly give when we don't understand the type or are unsure about why specific variables have multiple types. Now the question that arises here, is it a good approach? There is a thin line for it and understanding this thin line is what we will be doing in this blog.

Things you should know before moving to any JavaScript framework….jpg

Let's look at the following code and try to understand how the type any we use.

const exampleArr = []
function addElementInArray(element : any ){
    exampleArr.push(element)
}

addElementInArray("demo");
addElementInArray(1);
console.log(exampleArr) // ["demo, 1]

In the above example, we have one array exampleArr and then we have a function addElementInArray , which adds the element in an array. But here is one problem, the function doesn't know what type of element it is pushing inside an array. And the problem is that, every time we call the function, value we pass as argument has different types. First- it is a string and, second - it is a number. Now you might have thought that we can create this function by giving the type of parameter like this (number|string), as shown below.

const exampleArr = []
function addElementInArray(element : (number | string) ){
    exampleArr.push(element)
}

addElementInArray("demo");
addElementInArray(1);
console.log(exampleArr) // ["demo, 1]

But, here is a catch, what if the argument we pass to the function is boolean and I want to add that inside of that array? We need a more generic way to solve this problem. I repeat a more generic way…

Let's see what generic means and what its actual meaning is in simple English.

image.png

How should we do this in Typescript? Is there any way that, I can pass the types of my argument while calling the function? There must be a way that we can pass the type while calling and assign that type in the parameter of the function. Yes, there is a way, let's look at it below.

addElementInArray<string>("demo"); // here, type of argument is string
addElementInArray<number>(30) // here, type of argument is number
addElementInArray<boolean>(true) // here, type of argument is boolean

Definitely, you must have seen this kind of structure for calling a function. Now, how can we create a function in such a way that these types can be accessed? Let’s see:

const exampleArr = []
function addElementInArray<Type>(element : Type ){
    exampleArr.push(element)
}

In the above code, Type is a type of argument that is totally generic and passed when the function is being called. This thing is called Generics in typescript.

Generics allow us to write reusable code. It makes sure that input and output are of the same type.

I hope you have got why we should use generic. Let's see this from a different perspective.

Let's Look into some of the examples:

Creating a generic function

An Object has properties like name and email. But, what we want to do is add an userId in that object.

type userObjectTypes = {
    name: string;
    email: string;
}
const userObject : userObjectTypes= { 
    name: "Vijay", 
    email: "vijaytembugade21@gmail.com"
}

Let's add, and create a function that adds userId in the object.

const addUserId =(userObject)=>{
    const userId = Math.floor(Math.random()*100);
    userObject.userId = userId;
    return userObject;
}

But the problem in the above function is userObjectTypes does not have a type for usedID. So, let's add that.

type userObjectTypes = {
    name: string;
    email: string;
    userId?: number;
}

Now, we have to add this type in the function addUserId ’s parameters and how we can do that. That's where generic types come into play. Let's see the following example for that we can extend the type of userObject in the function.

const addUserId =<T extends userObjectTypes>(userObject : T ) : T=>{
    const userId = Math.floor(Math.random()*100);
    userObject.userId = userId;
    return userObject;
}

console.log(userObject) 
 // { name: 'Vijay', email: 'vijaytembugade21@gmail.com', userId: 64 }

Now, by this method, we created a function that extends the types of userObject and we don't need to write explicitly new types for the function.

Let's Create multiple objects keeping type same.

const multiObjectOne = {
    value :1,
    range: [1, 10],
    data : null,
}
const multiObjectTwo = {
    value :2,
    range: [20, 50],
    data : {isAdmin: false},
}
const multiObjectThree = {
    value :2,
    range: [20, 50],
    data : {isAdmin: true},
}
const multiObjectFour = {
    value :2,
    range: [20, 50],
    data : {isDisabled: true},
}

I want to give the types of the 4 objects mentioned above. Each object has three properties, name , range and data . From which name is a type of number and range is a type of array. But the type of data is not the same in every object. But, what we need to do is create the generic type which will be applicable to all 4 objects. Let's see how we can do that.

type MultiObjectType<Type> = { 
    value: number;
    range: number[];
    data: Type;
}

// in the above code Type is can be replaced by T for reading purposes.

here , we declared the MultiObjectType<Type>, and type is also passed in it.

Now we can define this type to each object and pass their types in it.

const multiObjectOne : MultiObjectType<null>= { // Type = null 
    value :1,
    range: [1, 10],
    data : null,
}
const multiObjectTwo : MultiObjectType<{isAdmin: boolean}> = { // Type = {isAdmin: boolean}
            value :2,
    range: [20, 50],
    data : {isAdmin: false},
}
const multiObjectThree  : MultiObjectType<{isAdmin: boolean}> = { // Type = {isAdmin: boolean}
    value :2,
    range: [20, 50],
    data : {isAdmin: true},
}
const multiObjectFour :  MultiObjectType<{myName: string}>= { //Type : {myName: string}
    value :2,
    range: [20, 50],
    data : {myName: "vijay"},
}

The above example shows us that we don't need to say specifically that type of data in an object is any.

We can also create generic methods and classes with help of generics in Typescript. Lets take a look at the following examples.

We will create a class that will create a data object and gives us data.

class Data<T> {
    private data : T[];

    constructor (){
        this.data = []
    }

    addData(value : T ): void{
        this.data.push(value)
    }

    getData(): T[] {
        console.log(this.data)
        return this.data;
    }
}

In the above example, I am not sure about what will the type of <T> which is why it can be reused.

Now I can create data collection of strings as shown bellow and access the addData and getData methods.

const newData = new Data<string>()
newData.addData("demo")
newData.addData("demo2")
newData.getData()  // [ 'demo', 'demo2' ]

If we try to add a numeric value in it it will give an error.

newData.addData(10) 
// error: Argument of type 'number' is not assignable to parameter of type 'string'.ts(2345)

But, we can use the same class for numeric value data collection.

const numbericData = new Data<number>() 
numbericData.addData(10)
numbericData.addData(20)
numbericData.getData() // [10, 20]

To get this, we don't have to write separate class for numeric data and we also do not need to explicitly say about any type in class. Similarly, we can create objects, and arrays of data collection without changing the class Data that is defined above. We just have to play with the value of T.

That is, we have to play with generics.

We can also have more than one type of parameter in generics. As shown below.

class Person<T, K> {
    private firstName: T;
    private lastName: T;
    private age: K;

    constructor(firstName: T, lastName: T, age: K){
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;  
    }
    getFullName() : string {
        return `${this.firstName} ${this.lastName}`
    }

    getAge() : string{
        return `${this.firstName}'s age is ${this.age}`
    }
}

const person = new Person<string, number>("John", "Doe", 54)
person.getFullName() // John Doe
person.getAge() // John's age is 54

So, in the above example T is string and K is number.

We can also pass the generic type to the specific function and get the value from it too.

class Data {
    private data : any[];

    constructor (){
        this.data = []
    }

    addData<T>(value : T ): void{
        this.data.push(value)
    }

    getData<T>(): T[] {
        console.log(this.data)
        return this.data;
    }
}

const d = new Data();
d.addData<number>(8);
d.addData<string>("string");
d.getData() // [8, "string"]

In the above example, we have added a generic type to function instead of class and which will also work fine if the collection can of random data types.

This is about generics in typescript. You can also read more about it in official docs.

If you like the blog, feel free to connect with me on Twitter and LinkedIn . Thank you