SyntaxStudy
Sign Up
TypeScript Generics in Classes and Static Members
TypeScript Beginner 1 min read

Generics in Classes and Static Members

Classes can be generic, accepting type parameters that parameterise the data they hold or manipulate. A generic class is like a blueprint for a family of classes. The type parameter is specified in angle brackets after the class name and can be used anywhere in the class body. Generic classes are ideal for implementing data structures like stacks, queues, and repositories. Static members belong to the class itself rather than to any instance. Static properties hold shared state, and static methods are utility functions that do not depend on instance data. TypeScript types static members separately from instance members, and you cannot refer to the class's type parameters in static members because statics are not tied to any particular instantiation. Class expressions and the use of classes as first-class values are supported in TypeScript with full type information. You can pass a class constructor as an argument, store it in a variable, or return it from a function. This enables patterns like mixins, which dynamically augment classes, and class registries, which manage a set of constructors.
Example
// Generic Stack class
class Stack<T> {
    private items: T[] = [];

    push(item: T): void   { this.items.push(item); }
    pop():  T | undefined { return this.items.pop(); }
    peek(): T | undefined { return this.items[this.items.length - 1]; }
    get size(): number    { return this.items.length; }
    isEmpty(): boolean    { return this.items.length === 0; }
}

const numStack = new Stack<number>();
numStack.push(1);
numStack.push(2);
console.log(numStack.peek()); // 2
console.log(numStack.pop());  // 2

// Static members — counter shared across instances
class IdGenerator {
    private static nextId = 1;

    static generate(): number {
        return IdGenerator.nextId++;
    }

    static reset(): void {
        IdGenerator.nextId = 1;
    }
}

console.log(IdGenerator.generate()); // 1
console.log(IdGenerator.generate()); // 2

// Mixin pattern
type Constructor<T = {}> = new (...args: unknown[]) => T;

function Timestamped<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        createdAt = new Date();
        updatedAt = new Date();
        touch() { this.updatedAt = new Date(); }
    };
}

class BaseModel { constructor(public id: number) {} }
const TimestampedModel = Timestamped(BaseModel);
const record = new TimestampedModel(1);
console.log(record.createdAt instanceof Date); // true