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.
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:
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.
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.
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.
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.
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.
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.
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.
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.