Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save masonmark/6372ef09074af82bb9478442b2e0eff3 to your computer and use it in GitHub Desktop.
Save masonmark/6372ef09074af82bb9478442b2e0eff3 to your computer and use it in GitHub Desktop.
How to reference a subclass type in an inheritied static method in TypeScript
/**
* Mason 2019-09-29: This is one way to allow subclasses to inherit a
* method with a parameter whose type refers to the actual subclass type
* (and not the parent class's type).
*
* One purpose here is to define a base class, that can have various
* subclasses, and enable all of the subclasses to inherit a class method
* named configure(), which creates an instance of the subclass and then
* configures the instance and its various subclass-specific properties.
*
* This gets weird in TypeScript, due to polymorphic 'this' not being
* available for static methods and constructors. This implementation was
* derived from the comment thread at:
*
* https://github.com/microsoft/TypeScript/issues/5863
*
* (That thread also shows several of the ways that trying to do this
* kind of thing can fail to work in TypeScript, which may be surprising.)
*
* This implementation uses the "fake this parameter" described here:
*
* https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#specifying-the-type-of-this-for-functions
*
* Combined with `InstanceType<T>` this allows us to capture the type in
* such a way that the configurator function works in subclasses, and the
* subclass properties can be auto-completed, etc.
*/
class Configurable {
static configure<T extends typeof Configurable>(this: T, configurator: ((x: InstanceType<T>) => void)): InstanceType<T> {
const result = (new this()) as InstanceType<T>;
configurator(result);
return result;
}
}
class Person extends Configurable {
name = "Alice";
age = 0;
get bio() {
return `My name is ${this.name}, and I am a ${this.constructor.name}. I am ${this.age} years old.`;
}
}
class FireFighter extends Person {
helmetSize?: number;
hasLicenseToDriveFireEngine = false;
}
class PoliceOfficer extends Person {
badgeNumber = "0000000";
}
const rand = Person.configure( x => {
x.age = 117;
x.name = "Rand";
});
const jane = PoliceOfficer.configure( x => {
x.name = "Jane";
x.age = 42;
x.badgeNumber = "8675309";
});
const configureBiff = (x: FireFighter) => {
x.hasLicenseToDriveFireEngine = true;
x.helmetSize = 15;
x.age = 21;
x.name = "Biff Wigginberger Jr.";
};
const biff = FireFighter.configure(configureBiff);
console.log(rand.bio);
console.log(jane.bio);
console.log(biff.bio);
// prints:
// My name is Rand, and I am a Person. I am 117 years old.
// My name is Jane, and I am a PoliceOfficer. I am 42 years old.
// My name is Biff Wigginberger Jr., and I am a FireFighter. I am 21 years old.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment