TypeScript Generics

typescript logoAbout a month ago, Microsoft released the 0.9 version of TypeScript. Originally released in October 2012, the language has come a long way and one of its most requested and recent additions is generics. You may find yourself asking, "When the heck am I going to use generics in JavaScript?" I had the same question. The answer is all the time!!

Even If You Don't Care

Even if you don't care about generics yourself, you'll start to get benefits from the community. For example, if you use the DefinitelyTyped definitions, those are being upgraded to 0.9 versions and generics are popping up. This means that if you use one of these definitions inside of Visual Studio, you'll get all the goodness of IntelliSense.

Prior to generics, you'd get information about a type and it would say: "any". Lovely. How descriptive. Now, you might get a description that says, "superEntity".

Array<T>

One of the most common uses you'll have for generics is when you want to create an array of a specific type. Of course, in JavaScript, the elements of an array don't need to be the same type. In TypeScript world, however, types are king and the same goes for typed arrays. Now we can declare an array of T such as:

var blah = new Array<T>();

The next time we try to use that array, by default we'll get all sorts of wonderful toys to play with. In Visual Studio, we'll get IntelliSense telling us exactly what type of array we've got and, because it knows what type the array is supposed to be, if you try to stuff something in there that isn't of that type, you'll get a compiler error (unless the signature of the other type has the same properties -- see below).
This was already sort-of in TypeScript before because you could define an array like this:

var myArray = MyModel[];

That worked in earlier versions of TypeScript -- if you try that now, you'll get a compiler error that asks you to do the Array<T> version.
typescript array generic

Interfaces

The interface is a great place to use generics. Let's create a base interface that has a couple of CRUD methods on it:

module TestModule {

    export interface IDataService<T> {
        getAll(): Array<T>;
        save(item: T);
    }

    export class Entity {
        message: string;
        id: number;
    }

    export class EntityService implements IDataService<Entity> {
        constructor() { }

        getAll(): Array<Entity> {
            return new Array<Entity>();
        }

        save(item: Entity) {

        }

    }
}

This allows us to write our data service specifically to the Entity type and get information about this type as we write it. Here's what it looks like in the editor:

typescript-generic-interface-typing

See? By using the generic interface, our implementation was forced into using all the right types. To make our life even easier, we're given the IntelliSense on the type in question. Isn't laziness awesome?

Continuing, let's say you've got some code that actually takes a service as a parameter. This happens a lot in modern JavaScript especially with frameworks like AngularJS where controllers and other MVC-ish patterns use dependency injection.

module TestModule {

    export interface IDataService<T> {
        getAll(): Array<T>;
        save(item: T);
    }

    export class Entity {
        message: string;
        id: number;
    }

    export class NotMyEntity {
        title: string;
        description: string;
    }

    // Notice this has the same structure of the original Entity
    export class AnotherEntity {
        message: string;
        id: number;
    }

    export class EntityController {
        constructor(public service: IDataService<Entity>) { }

        save(item: Entity) {

            var obj = new NotMyEntity();
            this.service.save(obj);

            var structuredObj = new AnotherEntity();
            this.service.save(structuredObj);

        }

    }

    export class NotMyEntityController {

        constructor(public notMyService: IDataService<NotMyEntity>) { }

        save(item: NotMyEntity) {
            this.notMyService.save(item);
        }

    }

}

We define a generic interface that's implemented somewhere else (perhaps the in first code block above). Our controller (NotMyEntityController) is dependent on a service that implements this interface for a specific data type, in this case NotMyEntity. Line 41 is where that dependency is declared. We use the service in the save method and all's good.

But take a look at the EntityController, specifically the save method.

export class EntityController {  
        constructor(public service: IDataService<Entity>) { }

        save(item: Entity) {

            var obj = new NotMyEntity();
            this.service.save(obj);

            var structuredObj = new AnotherEntity();
            this.service.save(structuredObj);

        }

    }

In the save() method, I take in an Entity, but when I use the service, I actually saved an item of type AnotherEntity. What gives?????

TypeScript is structurally typed.

This isn't new, but since AnotherEntity has the same signature as Entity, the compiler doesn't care that we've passed in a type of a different name. It's all the same. Earlier in the save() method, I try to use the service to save an object of type NotMyEntity. NotMyEntity does not have the same signature as Entity, so we get a compiler error:

typescript-generic-signature-matching

See? It doesn't complain that it's not of the same type. It complains that it doesn't have the same signature.

Overkill?

Is this just overkill for JavaScript? Maybe. But if you're used to being helped along by a compiler to fix your bugs before you get into production, this is a welcome tool. It seems a bit like overkill, but once you find yourself writing the same code over and over to satisfy specific entity controllers, you'll find that TypeScript generics really cut down on keystrokes and improve the structural quality of your code.

Remember, none of this type information is actually flowed in to the final JavaScript. The generics, they do nothing -- at least in the JavaScript. But in TypeScript world, they make a huge difference in programmer productivity.