Working with Generics in TypeScript

In this post on Working with Generics in TypeScript, I will explain the concept of Generics and how to create them in TypeScript.

Basically, generics allow us to create constructs that can work with any data type. In fact, we can create classes and functions without choosing a specific data type. Later when we run the code, we can specify a particular data type. Hence, if we create a generic function, its data type is decided at the runtime.

Use of generics also provide us the benefit of code reusability. We no longer need to develop the same code for different data types. Now let us look at the syntax for creating generics.

Creating Generics

A simple example of creating generics is given below. In fact, the function f() is an identity function that returns its argument. As shown below, we can call this function with a number type argument as well as a string type argument. It is important to note that we can call a generic function using an argument of a user-defined type like the object of a class called MyClass.

function f<T>(a:T):T{
	return a;
}

class MyClass{
  a:number;
  b:number;
  constructor(i:number, j:number){
	this.a=i;
	this.b=j;
  }
}
console.log(f<number>(10));
console.log(f<string>("Hello, World!"));

let ob:MyClass = new MyClass(12, 14);
console.log(f<MyClass>(ob));

Output

Example of Creating Generics
Example of Creating Generics

Example Using Arrays in a Generic Function

The following example shows how we can use a generic array as a parameter of a function. To illustrate the concept, we create an array of numbers and an array of strings using this generic function. Moreover, it is also possible to create an array of user-defined data types such as MyClass using the same generic function.

function f<T>(a:T[]):T[]{
	return a;
}

class MyClass{
  a:number;
  b:number;
  constructor(i:number, j:number){
	this.a=i;
	this.b=j;
  }
}

let arr1:number[]=[1,2,3,4,5];
let arr2:string[]=["ab", "bc", "cd", "de", "ef"];
console.log(f<number>(arr1));
console.log(f<string>(arr2));

let ob_arr:MyClass[] = [new MyClass(1,2),
new MyClass(3,4), new MyClass(5,6), new MyClass(7, 8), new MyClass(9, 10)];
console.log(f<MyClass>(ob_arr));

Output

Array Types in Generics
Array Types in Generics

Generics with Multiple Types

We can also have several generic argument types in a function or a class. An example of a generic class having two different generic types is given below.

Creating Classes

The use of generics is not only limited to the functions. In fact, we can have generic classes also. As evident from the following code, the generic class MyData holds a key-value pair. Therefore, first, we create a key using the object of the Student class and the value as a number which represents the marks of the student. Subsequently, we create the object of MyData using this key-value pair. After that, we create a key using the object of Employee class and value as a number representing the salary of the employee.

However, we can instantiate the generic class MyData using simple data types like number and strings also. In fact, the next four examples demonstrate the use of string and number in creating the objects of the class MyData.

class Student
{
	sid:number;
	sname: string;
	course: string;
	constructor(i:number, n:string, c:string)
	{
		this.sid=i;
		this.sname=n;
		this.course=c;
	}
}
class Employee
{
	eid:number;
	ename:string;
	designation:string;
	constructor(i:number, n: string, d:string)
	{
		this.eid=i;
		this.ename=n;
		this.designation=d;
	}
}
class MyData<T, U>
{
	key:T;
	value:U;
	constructor(K:T, V:U)
	{
		this.key=K;
		this.value=V;
	}
	getKey():T
	{
		return this.key;
	}
        getData():void
	{
		console.log("Key = ");
		console.log(this.getKey());
		console.log("Value = "+this.value);
	}
}

let ob1:Student = new Student(11, "Ashok", "MCA");
let ob2:Employee =new Employee(101, "Priya", "Developer");

console.log("A Student/Marks Key-Value Pair");
let d1:MyData<Student, number> = new MyData<Student, number>(ob1, 89.0);
d1.getData();

console.log("A Employee/Salary Key-Value Pair");
let d2:MyData<Employee, number> = new MyData<Employee, number>(ob2, 59000);
d2.getData();

console.log("A number/number Key-Value Pair");
let d3:MyData<number, number> = new MyData<number, number>(112, 10000);
d3.getData();

console.log("A string/number Key-Value Pair");
let d4:MyData<string, number> = new MyData<string, number>("Likes", 30000);
d4.getData();

console.log("A number/string Key-Value Pair");
let d5:MyData<number, string> = new MyData<number, string>(1, "India");
d5.getData();

console.log("A string/string Key-Value Pair");
let d6:MyData<string, string> = new MyData<string, string>("Tata", "Prima");
d6.getData();

Output

Demonstration of a Generic Class in TypeScript
Demonstration of a Generic Class in TypeScript

Generic Constraints

Suppose we want to create a generic class or function that is applicable only on some specific types then we can use generic constraints. As an illustration, the following example shows the use of generic constraints.

As shown we have a base class Person and its two derived classes Student and Employee respectively. Now that, we have created a hierarchy of classes, next we create a generic class MyData. However, our generic class has a constraint that it can only work with only those types which are derived from the class Person. Therefore, we can instantiate the class MyData only with any of the subclass of the class Person.

class Person
{
	pId:number;
	pName:string;
	pAge:number;
	pRole:string;
	constructor(i:number, n:string, a:number, r:string)
	{
		this.pId=i;
		this.pName=n;
		this.pAge=a;
		this.pRole=r;
	}
}
class Student extends Person
{
	course: string;
	constructor(i:number, n:string, a:number, r:string, c:string)
	{
		super(i,n,a,r);
		this.course=c;
	}
}
class Employee extends Person
{
	designation:string;
	constructor(i:number, n:string, a:number, r:string, d:string)
	{
		super(i,n,a,r);
		this.designation=d;
	}
}
class MyData<T extends Person>
{
	info:T;
	constructor(K:T)
	{
		this.info=K;
	}
	getRole=()=>this.info.pRole;
	getInfo():void
	{
		console.log(`General Information of ${this.getRole()}: `);
		console.log(this.info);
	}
}

let ob1:Student = new Student(11, "Ashok", 20, "Student", "MCA");
let d1:MyData<Student>=new MyData<Student>(ob1);
d1.getInfo();

let ob2:Employee =new Employee(101, "Priya", 30, "Employee", "Developer");
let d2:MyData<Employee>=new MyData<Employee>(ob2);
d2.getInfo();

Output

Demonstration of Generic Constraints
Demonstration of Generic Constraints

Working with Generics in TypeScript gives us Several Benefits

  1. We need not write a separate function for each and every data type. Hence, we are able to reuse functionality.
  2. Since we need to update the code only once, so it makes the application easy to maintain.
  3. Application development becomes faster.
  4. Generics also provide Type Safety.
  5. Unnecessary Type Checking is not required.

Summary

In this article on Working with Generics in TypeScript, you have learned the concept of Generics. In fact, generics provide us several benefits and one of the benefits is code reusability. Also, the use of generics in creating classes and functions makes the application development more productive. Lastly, it makes the software easy to maintain.


Related Topics

Introduction to Programming in TypeScript

Creating Classes in TypeScript

Working with Arrays in TypeScript

Significance of Tuples in TypeScript

Explaining Interfaces in TypeScript with Examples

How to Create and Use Arrow Functions in TypeScript

Using TypeScript Modules

Leave a Comment

Your email address will not be published. Required fields are marked *