Search code examples
typescriptooptyping

Annotate type as typeof subclass of base class


Given a Parent class with children ChildA ChildB

abstract class Parent {
    abstract foo()
}
class ChildA extends Parent {
    foo() { }
    bar() { }
}
class ChildB extends Parent {
    foo() { }
    baz() { }
}

How can I strictly type a function's parameter as being any class (the class itself, not an instance) that conforms to the type Parent, but is not itself Parent

fn(Parent) // Should fail, is supertype rather than subtype
fn(new ChildA()) // should fail, is instance of subtype rather than class itself
fn(new ChildB()) // should fail, is instance of subtype rather than class itself
fn(ChildA) // Should succeed
fn(ChildB) // Should succeed

Solution

  • You can define your fn as the following, using a construct signature:

    function fn(ctor: new (...args: any[]) => Parent) {}
    

    This describes a constructor that creates something which extends Parent (structurally, in the type system). Funnily enough, this is very close to the example in the documentation on abstract construct signatures.

    Passing in Parent will fail since Parent has an abstract constructor:

    fn(Parent);
    // ~~~~~~ Cannot assign an abstract constructor type to a non-abstract constructor type.
    

    Playground


    It may also help to mention a method that will work even if the Parent class is not abstract. You would need to check if the given class is the Parent or not first. Thankfully, this is possible, since types are inferred before being instantiated:

    function fn<Ctor extends new (...args: any[]) => Parent>(ctor: typeof Parent extends Ctor ? never : Ctor) {}
    

    Playground