Search code examples
typescriptabstract-classtypescript-generics

Get child type in abstract class constructor


I have an abstract class that will take any data passed into the constructor and assign it to the instance. The goal being that you can initialize an instance with a subset of its data.

The problem I'm having is that the once the abstract class has been extended, the child class constructor expects an argument of type Partial<AbstractParent> instead of Partial<Child>.

I've been able to mimic this pattern with a static create method, generics, and usage of the this param, but I would much prefer if I was able to do this with the constructor.

Minimal reproducible example

interface ResourceConstructor {
  new(data: Partial<Resource>): Resource

  create<T extends ResourceConstructor>(this: T, data: Partial<InstanceType<T>>): InstanceType<T>;
}

abstract class Resource {
  public abstract id: number;

  constructor(data: Partial<Resource>) {
    Object.assign(this, data);
  }

  static create<T extends ResourceConstructor>(this: T, data: Partial<InstanceType<T>>): InstanceType<T> {
    const instance = new this(data) as InstanceType<T>;
    return instance;
  }
}

class MyResource extends Resource {
  id: number = 1;
  someField: string = 'value';
}

const myResource = new MyResource({ someField: 'hello world' }); 
//this produces an error "Argument of type '{ someField: string; }' is not assignable to parameter of type 'Partial<Resource>'"

const anotherResource = MyResource.create({ someField: 'hello world' });

Solution

  • Conceptually you'd like the constructor to accept a parameter of type Partial<this>, using the polymorphic this type. Unfortunately, you cannot currently use polymorphic this in parameter types for the static side of a class, which includes the constructor method. There are longstanding open feature requests for this at microsoft/TypeScript#5863 and more specifically at microsoft/TypeScript#38038. Until and unless these get implemented, you can't express what you want directly.

    For now, you'd need to just work around it. One popular workaround, as mentioned in those issues, is to use a generic static method with a this parameter. Of course this means you'd need to use Ctor.create() instead of new Ctor(). I'd explain how to do it, except that you've already found this workaround and implemented it in your example code. Oh well, I'm afraid there's nothing particularly better than this at the moment.