How can I write a function that expect a class as parameter with a private constructor

Viewed 52

(playground here)

let's say I want to write a function that accepts a class as input (I am writing a mixin following the example in the handbook).

type Constructor = new (...args: Array<any>) => {};

function myFunction<TBase extends Constructor>(Base: TBase) {
    return class Child extends Base {
        // ...
    };
}

I can use it with "normal" classes

class C {
    // ...
}
const C2 = myFunction(C);

But the compiler gives me an error if I try to call myFunction on a class with a private constructor

class E {
    private constructor() {
        // ...
    }
}
const E2 = myFunction(E);

with the error Cannot assign a 'private' constructor type to a 'public' constructor type.

but how can I change Constructor (well, the type of Base) to say that "it should be subclassable" and that should accept also classes with a private constructor?

1 Answers

It's not possible. You can't extend classes with private constructors by design.

class E {
   private constructor(): {}
}

class F extends E { } // Cannot extend a class 'E'. Class constructor is marked as private.

If your goal is to make it impossible to instantiate the class by a user, but only its children made with myFunction, you can ask for a protected constructor. Unfortunately, it can't be requested directly, so you have to do some hacky type inferring.

abstract class ProtectedConstructorSource {
    protected constructor (...args:any[]) {};
} 

type ProtectedConstructor = typeof ProtectedConstructorSource;
type PublicConstructor = new (...args: any[]) => {};
type Constructor = ProtectedConstructor | PublicConstructor;

So, now you can pass a class with a protected constructor as an argument to myFunction. But instead of constraining it to ProtectedConstructor you have to constrain it to Constructor type. In this case, TypeScript will allow you to extend it.

function myFunction<TBase extends Constructor>(Base: TBase): PublicConstructor {
    return class Child extends Base {
        public constructor() {
            super();
            console.log("Hello! I am the child of the class with protected constructor");
        }
    };
}

class E {
    protected constructor() {
        console.log("Hello! I am a protected constructor.");
    }
}

const E2 = myFunction(E);
new E2();

Check it at TypeScript Playground.

Related