Generics in TypeScript

Adesh Kumar

Following the DRY principle, one should write dynamic and reusable code. Generics are not a new term in the programming language. In fact, it follows the DRY principle very well.

Generics in TypeScript come in handy. You can write beautiful, dynamic, and reusable generic blocks of code using generics. Furthermore, you can apply generics in TypeScript to classes, interfaces, and functions.

What are Generics?

Sometimes you come across a situation to repeat the same block of code for different types. So, instead of writing repeated blocks of code, generics let you write a generalized form of method.

Although, there is a type called any, which you can use to achieve the same effect as generics in your code, any is not type-safe, and chances are that your application can get an exception.

To clarify, take a look at the below example to better understand the above case.

// for number type
function fun(args: number): number {
  return args;
}

// for string type
function fun(args: string): string {
  return args;
}

Note that, here we are repeating the same function for number and string types. In this case, there is one way to make it generic –– using type any.

To show, let’s see how to make it generic using type any.

// for any type
function fun(args: any): any {
 return args;
}

While swapping number and string types to type any, we achieved our goal to make it generic, there’s a catch –– it can accept any kind of data and as a result, we are losing type safety as well.

In short, the TypeScript compiler doesn’t know about its data type passed to this function.

🐊 Alligator.io recommends

Our recommended TypeScript courses

Make Them Type-Safe

To solve the above problem and make it type-safe again, we’ll use Type parameters. Type parameters are, generally, defined by T or <T>. They denote the data type of passed parameters to a class, interface, and functions.

To show, let’s use T to make our generic function type-safe.

function fun<T>(args:T):T {
  return args;
}

As a result, it’s now type-safe. Let’s see how to use the same function for string and number type.

// for string
let result = fun<string>("Hello World");

// for number
let result = fun<number>(200);

Generics With Many Types Parameters

If there are many parameters to a function, you can use other alphabets to denote the types. There is no restriction to use only T.

In other words, you can use your favorite alphabet. Let’s see how to work with many types.

function fun<T, U, V>(args1:T, args2: U, args3: V): V {
  return args3;
}

Like with functions, we can use generics with classes and interfaces as well. Let’s explore them one by one.

Generic Classes

Like generic functions, we can make our classes generic as well. We’ll pass the type parameter, as usual, in angle (<>) brackets. Then use it across the class for defining methods and properties.

Let’s say we want to create a class that can take numbers or string input and creates an array out of it.

class customArray<T> {
  private arr: T[];

  getItmes : T[] {
    return this.arr;
  }

  addItem(item:T) {
   this.arr.push(item);
  }

  removeItem(item: T) {
    let index = this.arr.indexOf(item);
     if(index > -1)
       this.arr.splice(index, 1);
 }
}

Now our generic class is ready. Let’s see how to use it for numbers and string data types.

let numObj = new customArray<number>();
numObj.addItem(10);

let strObj = new customArray<string>();
strObj.addItem(Robin);

Note that we have used our class for both number and string types. You can play with the methods of this class.

Generic Constraints

Up until this point, we are pretty much clear about TypeScript Generics, but there is one drawback I would like to bring your attention. But before that, let’s have a look at the below example.

We’re going to write a function, which will return the length of a function’s argument.

function getLength<T>(args: T) : number {
  return args.length;
}

This function will work as long as the passing type as a length property. But what about other types, which don’t have a length property. Guess what? You’re right, it will throw an exception!

Here’s the solution — putting generic constraints. Let’s see how to put the generic constraints to avoid such exceptions.

First, we need an interface and define a length property.

interface funcArgs {
  length: number;
}

Now, change our above function and extend it with this interface to the constraint.

function getLength<T extends funcArgs>(args:T) : number {
  return args.length;
}

We’ve created a generic constraint using an interface. Furthermore, we also extended our function getlength() with this interface. It now needs length as a required parameter.

In this case, accessing our function with an argument that doesn’t have a length parameter will show an exception message.

let result = getLength(3);  // throw an exception

Here is the right way of calling the above function with length parameter.

let result = getLength({ length: 5, name: 'Hello'});

This is the right way of calling our function. This call has a length property, and our function will work well. It will not show any error message.

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#