Typescript: How to set method return value => subclass

You can have this as a return type and you can access the constructor of your current object with this.constructor. That allows you to work more easily with subclassed immutables.

abstract class Point {
    public readonly x: number;
    public readonly y: number;

    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }

    diff(point: Point): this {
        return this.update(this.x - point.x, this.y - point.y);
    }
    // many methods like diff(); and then...

    protected abstract update(x: number, y: number): this;
}



class ImmutablePoint extends Point {
    protected update(x: number, y: number): this {
        return new (<any>this.constructor)(x, y);
    }
}



class MutablePoint extends Point {
    public x: number;
    public y: number;

    protected update(x: number, y: number): this {
        this.x = x;
        this.y = y;
        return this;
    }
}


const pointA: ImmutablePoint = new ImmutablePoint(10, 10)
const pointB: ImmutablePoint = new ImmutablePoint(6, 2);
const result: ImmutablePoint = pointA.diff(pointB);

You can make Point generic:

abstract class Point<T extends Point<any>> {
    public readonly x: number;
    public readonly y: number;

    constructor(x: number, y: number) {
        ...
    }

    diff(point: Point<any>): T {
        return this.update(this.x - point.x, this.y - point.y);
    }

    protected abstract update(x: number, y: number): T;
}

class ImmutablePoint extends Point<ImmutablePoint> {
    protected update(x: number, y: number): ImmutablePoint {
        return new ImmutablePoint(x, y);
    }
}

class MutablePoint extends Point<MutablePoint> {
    public x: number;
    public y: number;

    protected update(x: number, y: number): MutablePoint {
        ...
        return this;
    }
}


const pointA: ImmutablePoint = new ImmutablePoint(10, 10)
const pointB: ImmutablePoint = new ImmutablePoint(6, 2);
const result: ImmutablePoint = pointA.diff(pointB); // fine

(code in playground)

With the new Default generic type variables feature available you should be able to do something like:

abstract class Point<T extends Point = Point> {
  ...
}

(haven't tested it yet)

Tags:

Typescript