3.10 Generics and generic constraints

Wikipedia describes generic types as follows: In a generic type, a type parameters (T) is a placeholder for a specific type that a client specifies when they instantiate a variable of the generic type.

Getting Ready

All you need to be able to use generics is an installation of TypeScript 2.0 or higher.

How to do it…

We are going to start by declaring an interface and a type. We are going to declare this type just to be able to showcase a real life example of generics without having to go into too much detail.

interface IUtils { postAsync<T>(url: string, entity: T): boolean; }
declare var utils: IUtils;

Now that we have the type declarations ready we can proceed to implement a generic class named Service.

class Service<T> {
	private _url: string;
	
	constructor(url: string) {
		this._url = url;
	}
	
	save(entity: T): boolean {
     return utils.postAsync<T>(this._url, entity);
	}

   //...
}

How it works…

Sometimes we will encounter cases in which we will find our selves writing some classes around our entities. If we are working on the implementation of things like a database repository or a web service client, we will probably write almost identical pieces of code. Sometimes, the only difference between one repository class or a web service client and another would be the types of the entities. In those scenarios we can replace the types with a generic type (T) and specify it when creating the instances of the generic class:

class Client { email: string; }
const clientService = new Service<Client>('/api/client');
clientService.save(new Client());

class Order { orderId: number; }
const orderService = new Service<Order>('/api/order');
orderService.save(new Order());

There’s more…

Sometimes you will need to invoke a method or access a property of the generic type T which is not part of the Object prototype. In that case, you will need to use a generic type constraint to limit the types that can be used as the generic type (T). You should start by declaring an interface:

interface IValidatable {
	isValid(): boolean;
}

And continue by the creation of a generic constraint:

class Service<T extends IValidatable> {
	private _url: string;
	
	constructor(url: string) {
	  this._url = url;
	}
	
	save(entity: T): boolean {
	  if(entity.isValid()) {
	    return utils.postAsync<T>(this._url, entity);
	  }
	}
}

Once we have declared the generic class constraint we will need to ensure that all the entities that will be used as the generic type (T) satisfy that constraint.

class Client implements IValidatable {
	email: string;
	isValid() { return true };
}
const clientService = new Service<Client>('/api/client');
clientService.save(new Client());

class Order implements IValidatable {
	orderId: number;
	isValid() { return true };
}
const orderService = new Service<Order>('/api/order');
orderService.save(new Order());

Source Code

Generics and generic constraints


Shiv Kushwaha

Author/Programmer