TypeScript - Generics



Generics are a powerful feature in TypeScript that allow you to write reusable code that can work with different types of data. They act like placeholders that can be filled with specific data types when you use the generic code. This improves code flexibility and maintainability.

Problem Examples

Before going deep into TypeScript generics, let's understand the problem examples where you need to apply generics.

Let's start with the example below, where you want to log the value of the variable passed as a parameter.

Example

In the code below, we have defined the printVar() function which takes the number value as a parameter and logs the value in the console. Next, we invoke the function by passing 10 as an argument.

function printVar(val: number) {
    console.log(val); // Prints the value of val
}
printVar(10); // Invokes the function with a number

On compiling, it will generate the following JavaScript code.

function printVar(val) {
    console.log(val); // Prints the value of val
}
printVar(10); // Invokes the function with a number

Output

Its output is as follows –

10

Now, let's suppose you want to extend the use case of the printVar() function to print the value of other types of variables like string, boolean, etc. One way of doing that is as shown in the example below.

Example

In the code below, the printVar() function can accept the arguments of number, string, or boolean type.

function printVar(val: number | string | boolean) {
    console.log(val); // Prints the value of val
}
printVar(true); // Invokes the function with a boolean value

On compiling, it will generate the following JavaScript code.

function printVar(val) {
    console.log(val); // Prints the value of val
}
printVar(true); // Invokes the function with a boolean value

Output

The output is as follows –

true

What if you want to print the array or object value? You need to extend the types of the 'val' parameter, and it makes the code complex to read.

Another way to use the parameters of 'any' data type is as shown in the example below.

Example

In the code below, the type of the 'val' parameter is any. So, it can accept any type of value as an argument.

function printVar(val: any) {
    console.log(val); // Prints the value of val
}
printVar("Hello world!"); // Invokes the function with a boolean value

On compiling, it will generate the following JavaScript code –

function printVar(val) {
    console.log(val); // Prints the value of val
}
printVar("Hello world!"); // Invokes the function with a boolean value

Output

Its output is as follows –

Hello world!

The problem with the above code is that you won't have a reference to the data type inside the function. Whether you pass a string, number, boolean, array, etc. as a function argument, you will get the 'any' type of the variable in the function.

Here, generic functions come into the picture.

TypeScript Generics

In TypeScript, generics is a concept that allows to creation of reusable components like functions, classes, interfaces, etc. It creates a function, classes, etc. which can work with multiple data types instead of the single data type. In short, it allows developers to create programs that can work with multiple data types and are scalable in the long term.

Syntax

Users can follow the syntax below to use the generic variables with functions in TypeScript.

function printVar<T>(val: T) {
    // execute the code
}
printVar(val);
  • Developers can use the type variable in the angular bracket(<>) after the function name.

  • After that, you can use the type variable T as a type of the parameters.

  • Here, developers can use any valid identifier instead of 'T'.

  • After that, you can call the function with the value of any data type, and the function automatically captures the data type of the variable.

Example

In the example below, the printVar() function is a generic function, which takes the value of any data type as an argument, and prints it.

After that, we have invoked the function with array, object, and boolean value. In the output, users can observe that it prints the value of different types of variables without any error.

function printVar<T>(val: T) { // T is a generic type
    console.log("data: ", val);
}
let arr = [1, 2, 3];
let obj = { name: "John", age: 25 };

printVar(arr); // Val is array
printVar(obj); // Val is Object
printVar(true); // Val is boolean

On compiling, it will generate the following JavaScript code.

function printVar(val) {
    console.log("data: ", val);
}
let arr = [1, 2, 3];
let obj = { name: "John", age: 25 };
printVar(arr); // Val is array
printVar(obj); // Val is Object
printVar(true); // Val is boolean

Output

The output of the above example code is as follows –

data:  [ 1, 2, 3 ]
data:  { name: 'John', age: 25 }
data:  true

Example

In this code, we printVar() function is a generic function, which takes the type of the variable value passed as a parameter. While invoking the function, we have passed the value of different data types, and users can observe the type of each variable in the output.

function printVar<T>(val: T) { // T is a generic type
    console.log("data: ", typeof val);
}

printVar(2); // Val is number
printVar("Hello"); // Val is string
printVar(true); // Val is boolean

On compiling, it will generate the following JavaScript code.

function printVar(val) {
    console.log("data: ", typeof val);
}
printVar(2); // Val is number
printVar("Hello"); // Val is string
printVar(true); // Val is boolean

Output

The output of the above example code is as follows –

data:  number
data:  string
data:  boolean

Example

In the code below, the concatenate() function takes two parameters of type T and U, respectively. It uses the spread operator to concatenate the value of the 'first' and 'second' parameters.

Next, we call the function to concatenate two strings and arrays. In the output, we can observe that the concatenate() function executes without any error and prints the final output in the console.

function concatenate<T, U>(first: T, second: U): T & U {
    return {...first, ...second};
}

// Example usage with strings
const resultString = concatenate("Hello, ", "world!");
console.log(resultString); // Output: Hello, world!

// Example usage with arrays
const resultArray = concatenate([1, 2, 3], [4, 5, 6]);
console.log(resultArray); // Output: [1, 2, 3, 4, 5, 6]

On compiling, it will generate the following JavaScript code.

function concatenate(first, second) {
    return Object.assign(Object.assign({}, first), second);
}
// Example usage with strings
const resultString = concatenate("Hello, ", "world!");
console.log(resultString); // Output: Hello, world!
// Example usage with arrays
const resultArray = concatenate([1, 2, 3], [4, 5, 6]);
console.log(resultArray); // Output: [1, 2, 3, 4, 5, 6]

Output

The output of the above example code is as follows –

{
  '0': 'w',
  '1': 'o',
  '2': 'r',
  '3': 'l',
  '4': 'd',
  '5': '!',
  '6': ' '
}
{ '0': 4, '1': 5, '2': 6 }

Benefits of Generics

Here are some benefits of using generics in TypeScript.

  • Type Safety: Generics enforce type consistency, reducing runtime errors by catching mistakes at compile time.

  • Code Reusability: Developers can define a single generic function, class, or interface that works with different data types. It reduces the code duplication.

  • Improved Readability: By using Generics, developers can write cleaner and easy-to-read code.

  • Enhanced Performance: You can increase the performance of the application by avoiding unnecessary type casting and checks via using generics.

Advertisements