How To Create A Parent Constructor That Uses Generics To Allow Object Initialization For Children In Typescript?

17 Views Asked by At

Okay so I've got a Typescript typing question that I can't quite figure out.

I want to create a parent class that allows all inheriting classes to do C# style object initializations.

Background

Normally when initializing an object with data in TS you'd do something like this:

class Person {
  firstName: string;
  lastName: string;
}

const person = new Person();
person.firstName = "Fred";
person.lastName = "Flintstone";

Not the worst thing ever, but its a kind of clunky syntax (especially if the class was much more complex than this). Using a constructor and type Partial we can define a cleaner way to initialize an object with data:

class Person {
  firstName: string;
  lastName: string;

  constructor(init?: Partial<Person>) {
    Object.assign(this, init);
  }
}

const person = new Person({
  firstName: "Fred",
  lastName: "Flintstone"
});

This is great! The Partial means we only need to provide the data we want to at time of instantiation and that theres no conflicts with functions etc. And again, at least to my eyes and especially on larger classes, this reads much more cleanly than doing one-by-one property assignments. But now we've got this sort of funky constructor sitting in our class definition that doesn't really need to be there, and what if we want to apply this behaviour to many classes?

Question:

How could I create a base class that I could extend to provide this behaviour to multiple classes while retaining some level of type awareness on the Partial<ClassTypeHere> part? So That i could do something like this:

class Initializeable {
  constructor(init?: Partial<??????????>) { // <- the typing here is the real conundrum
    Object.assign(this, init);
  }
}

class Person extends Initializeable {
  firstName: string;
  lastName: string;
}

class Car extends Initializeable {
  make: string;
  model: string;
}

const person = new Person({
  firstName: "Fred",
  lastName: "Flintstone"
});

const car = new Car({
  make: "Ford",
  model: "Model T"
});

What I Know So Far:

So in my research so far, it feels like some usage of Generics could be used to solve this problem, but I can't quite wrap my head around what would enable me to define a partial generic of whatever the child type is.

For example with

class Initializeable {
  constructor(init?: Partial<??????????>) { // <- I'd want the type init to map to the child type
    Object.assign(this, init);
  }
}

So if Person extends Initializable, the partial type in the constructor should end up being Person.

I had naively hoped it would be as simple as constructor(init?: Partial<this>) or constructor(init?: Partial<typeof(this)>) or something like that, but that of course didn't work for obvious reasons. Nor is it as simple as constructor(init?: Partial<T>) because again, of course Typescript has no idea what the random T type I'm shoving in there is.

I've messed around with other more complex constructs to try to capture a self-type that would be accessible by the constructor, but I started to get out of my TS Type system depth pretty quick.

Am I totally barking up the wrong tree here? Do I need to accept that this is just not the way typescript does things?

0

There are 0 best solutions below