Typescript generic

Aug 07, 2019

If your goal when adopting TypeScript is to create an API for client consumption, then generics are the language construct you should become the most familiar with. They allow consumers of frameworks and APIs to agree on a specific contract to accomplish a designated goal without having to know specific object types. Generics define the third and final category of objects in TypeScript, type parameters. As we saw earlier in this chapter when we discussed arrays, type parameters are defined inside of < and > characters. They can be used on interfaces or class definitions to create functionality for a broad set of object types that may or may not be known at the time of development. Let's say we wanted to create a generic task processor. We will assume the simplest possible implementation, which is just a sequential process that passes a list of generic tasks to a function to have them run:

interface ITask {
    Id: number;
    Execute(): boolean;
    Error: string;
}
function ProcessTasks<T extends ITask>(tasks: T[]): T {
    for (var i = 0; i < tasks.length; i++) {
        if (tasks[i].Execute() == false) {
            return tasks[i];
        }
    }
    return null;
}

In this example, we have defined an interface that the task processor will rely on to execute a set of tasks sequentially. The function declaration for ProcessTasks now contains a type parameter, T, which must adhere to the ITask interface. This type parameter is used as a constraint for the objects that can be passed in as part of the tasks array, and will be the return type of the function. The tasks array is looped through one by one and the Execute method is called. If the Execute method returns a false value then the processing is stopped and the task is returned. Generics are built completely on type annotations, so they have no effect on the generated JavaScript. However, if the type constraints are not met, a compile error will be generated.

Interfaces and classes are capable of creating even more complex structures built around a very loose type definition. A type parameter provided to an interface or class can be used to provide type information for any member or function inside of the declaration body. In the next example, our ProcessTasks function has been moved into a class that will operate as the processor. A generic interface is created that will allow us to create any number of task processors with different implementations of the ProcessTasks function. Then, we see a class declaration that provides its own set of type parameters and implements the ITaskProcessor interface:

interface ITask {
    Id: number;
    Execute(): boolean;
    Error: string;
}
interface ITaskProcessor<T extends ITask> {
    ProcessTasks(tasks: Array<T>): T;
    CurrentTask: T;
}
class TaskProcessor<T extends ITask> implements ITaskProcessor<T> {
    public CurrentTask: T = null;
    constructor() {
    }
    public ProcessTasks(tasks: T[]): T {
        for (var i = 0; i < tasks.length; i++) {
            this.CurrentTask = tasks[i];
            if (this.CurrentTask.Execute() == false) {
                return tasks[i];
            }
        }
        this.CurrentTask = null;
        return null;
    }
}

Generic functions and types are not limited to a single unknown type. Type parameters can be lists of type information that can be used anywhere within the declaration block that the list is associated with. Creating a list of type parameters is the same as passing a single type parameter, only each type is separated by a comma. Furthermore, each generic type created in a parameter list can be used as a base type for other type parameters inside of the type's definition. The next example shows both of these concepts:

interface IExample<T, U extends HTMLElement> {
    Operate<V extends U>(): T;
}

As you can see, there is a comma-separated list of generic types and any constraints they might have instead of the single generic type. The Operate function then requires a type that inherits from the U type that was provided in the initial type parameter list.

Neeraj Dana

Experienced Software Engineer with a demonstrated history of working in the information technology and services industry. Skilled in Angular, React, React-Native, Vue js, Machine Learning