To solve your issue, you have a couple of options. One way is to inform Typescript that the superclass
can be instantiated using the following syntax, as mentioned briefly in the TypeScript handbook:
// "new (...args: any[]) => any" indicates that the constructor accepts any number of arguments
// and returns anything
function createNameableSubclassOf<C extends new (...args: any[]) => any>(superclass: C) {
return class extends superclass implements INameable {
name?: string = name
}
}
This approach allows the compiler to deduce a suitable, albeit somewhat opaque type for the output of createNameableSubclassOf
:
const NameableProduct = createNameableSubclassOf(Product)
// const NameableProduct: {
// new (...args: any[]): createNameableSubclassOf<typeof Product>.(Anonymous class);
// prototype: createNameableSubclassOf<any>.(Anonymous class);
// } & typeof Product 🙁
const nameableProduct = new NameableProduct();
// const nameableProduct: createNameableSubclassOf2<typeof Product>.(Anonymous class) & Product; 🙁
const productName = nameableProduct.name;
// const productName: string | undefined; 🙂
Alternatively, if you prefer a more clear-cut type that doesn't rely on anonymous classes, you can specify the superclass using generics and utilize conditional types to extract the constructor's parameters and return types:
function createNameableSubclassOf<C extends new (...args: any[]) => any>(
superclass: C
): C extends new (...args: infer A) => infer T ? new (...args: A) => T & INameable : never;
function createNameableSubclassOf(
superclass: new (...args: any[]) => any
): new (...args: any[]) => INameable {
return class extends superclass implements INameable {
name?: string = name
}
}
Note the use of a single function signature overload for the caller's convenience. The implementation signature is less strict to better accommodate conditional types. This design choice enhances type safety for the caller without requiring numerous type assertions within the implementation.
While this option may involve more code, it results in clearer types when utilized:
const NameableProduct = createNameableSubclassOf(Product)
// const NameableProduct: new () => Product & INameable 🙂
const nameableProduct = new NameableProduct()
// const nameableProduct: Product & INameable 🙂
const productName = nameableProduct.name
// const productName: string | undefined 🙂
I trust that one of these approaches will prove beneficial to you. Best of luck with your project!