Back to roadmaps typescript Course

Generic Classes and Tools

Just like functions and interfaces, classes can also be generic. This is highly useful when building data collections, state containers, or data access layers.


1. Creating a Generic Class

Let us build a type-safe DataRepository class that manages an array of items of any type.

class DataRepository<T> {
  private data: T[] = [];

  public add(item: T): void {
    this.data.push(item);
  }

  public getAll(): T[] {
    return this.data;
  }
}

Instantiating the Class

You can instantiate a generic class by specifying the type parameters in angle brackets, or let TypeScript infer it:

// Explicit type definition
const textRepo = new DataRepository<string>();
textRepo.add("hello");

// Compiler prevents adding numbers to a string repository
// textRepo.add(42); 

2. Generic Class Constraints

You can restrict class types using constraints, similar to functions.

interface Identifiable {
  id: string;
}

class EntityStore<T extends Identifiable> {
  private store = new Map<string, T>();

  public save(entity: T): void {
    this.store.set(entity.id, entity);
  }

  public get(id: string): T | undefined {
    return this.store.get(id);
  }
}

3. Generic Utility Helpers

Generics are excellent for writing utility functions that wrap async behavior. For example, a fetch wrapper that casts the response payload:

async function fetchJSON<T>(url: string): Promise<T> {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error("Network response error");
  }
  return response.json() as Promise<T>;
}

4. Summary

  • Classes use <T> in their declaration header to introduce type parameters.
  • Generic classes manage state and logic consistently across multiple input types.
  • Apply constraints (using extends) to restrict generic class instances to compatible objects.
  • Leverage generic return types (like Promise<T>) to type-wrap asynchronous actions safely.
Published on Last updated: