Generics In TypeScript

In this post I want to explore all the functionality available in TypeScript to use generics.

Let’s start with the simples example of creating strongly types arrays. 

module generics {
   
   
class Person {
       
name: string;
       
parents: Person[];
   
}

}

Now, you can only add a person to parents collection.  It is very simple, but you cannot for example add a string to that collection:

module Generics {
   
   
class Person {
       
name: string;
       
parents: Person[];
       
constructor(name: string) {
           
this.parents = [];
           
this.name = name;
       
}
   
}

    var boy: Person = new Person(“Jack”);
   
var mom: Person = new Person(“Jill”);
   
boy.parents.push(mom);
   
alert(boy.parents[0].name);

}

As you can see, I do need to strongly type both variables, boy and mom to take full advantage of the type system.  At that point, I cannot do something like boy.parents.push(“”);

It is important to notice that for strongly typed arrays you can use interfaces as well.

module Generics {

    interface IPerson {
       
name: string;
   
}
   
   
class Person implements IPerson {
       
name: string;
       
parents: IPerson[];
       
constructor(name: string) {
           
this.parents = [];
           
this.name = name;
       
}
   
}

    var boy: Person = new Person(“Jack”);
   
var mom: Person = new Person(“Jill”);
   
boy.parents.push(mom);
   
alert(boy.parents[0].name);

}

You can also have generic classes, similarly to C#.  For example, you can create Parent generic class that contains collection of children of generic types.

    class Parent<T> {
       
children: T[];
       
constructor() {
           
this.children = [];
       
}
   
}

    var parent: Parent<IPerson> = new Parent<IPerson>();
   
parent.children.push(boy);
   
alert(parent.children[0].name);

The example above seems to be quite common use case for me.  Of course, you do not need to have strongly types collection property on a parent.  You can also have single property.

    class SingleChildParent<T> {
       
child: T;
       
constructor() {
       
}
   
}

    var singleChildParent: SingleChildParent<IPerson> = new SingleChildParent<IPerson>();
   
singleChildParent.child = boy;
   
alert(parent.children[0].name);

Now, let’s take a look at generic functions.  For example, you can add a function to a parent class to act on a child as a parameter.

    class SingleChildParent<T> {
       
child: T;
       
setChild(child: T) {
           
this.child = child;
       
}
       
constructor() {
       
}
   
}

    var singleChildParent: SingleChildParent<IPerson> = new SingleChildParent<IPerson>();
   
singleChildParent.setChild(boy);
   
alert(parent.children[0].name);

As you can see, I added a strongly typed setChild function that is typed to the class’s generic parameter.  Of course, you can add types onto function itself as well.

    class SingleChildParent<T> {
       
child: T;
       
setChild(child: T) {
           
this.child = child;
       
}
       
convertToName<TParam extends IPerson>(parameter: TParam): string {
           
return parameter.name;
       
}
       
constructor() {
       
}
   
}

    var singleChildParent: SingleChildParent<IPerson> = new SingleChildParent<IPerson>();
   
singleChildParent.setChild(boy);
   
alert(singleChildParent.convertToName(boy));

In the example above convertToName function is using a couple of new features.  First of all, there is a generic parameter on a function itself.  Secondly, I added generic types constraint onto it, making sure that generic parameter implements an interface.  You can also have function’s return values to be generic.

    class SingleChildParent<T> {
       
child: T;
       
setChild(child: T) {
           
this.child = child;
       
}
       
getChild(): T {
           
return this.child;
       
}
       
convertToName<TParam extends IPerson>(parameter: TParam): string {
           
return parameter.name;
       
}
       
constructor() {
       
}
   
}

    var singleChildParent: SingleChildParent<IPerson> = new SingleChildParent<IPerson>();
   
singleChildParent.setChild(boy);
   
alert(singleChildParent.getChild().name);

In the example above getChild function has return value typed to the owning class’s generic parameter.  Of course you can also have return value type as a generic parameter to the function itself.

class SingleChildParent<T> {
       
child: T;
       
static getParent<TParent extends SingleChildParent<IPerson>, TChild extends IPerson>(person: TParent): TChild {
           
return null;
       
}
       
constructor() {
       
}
   
}

The example above is a bit contrived, but it illustrates the point of having multiple generic parameters, one of each is a function parameter’s type and the other function’s return value type.

I hope you had fun with generic in TypeScript with me.

Enjoy.

Leave a Reply

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