How does interfaces with construct signatures work?
Construct signatures in interfaces are not implementable in classes; they're only for defining existing JS APIs that define a 'new'-able function. Here's an example involving interfaces new
signatures that does work:
interface ComesFromString { name: string;}interface StringConstructable { new(n: string): ComesFromString;}class MadeFromString implements ComesFromString { constructor (public name: string) { console.log('ctor invoked'); }}function makeObj(n: StringConstructable) { return new n('hello!');}console.log(makeObj(MadeFromString).name);
This creates an actual constraint for what you can invoke makeObj
with:
class Other implements ComesFromString { constructor (public name: string, count: number) { }}makeObj(Other); // Error! Other's constructor doesn't match StringConstructable
On my search for the exact same question I went looking how the TypeScript-Team did that...
They are declaring an interface and afterwards a variable with a name matching exactly the interface-name. This is also the way to type static functions.
Example from lib.d.ts
:
interface Object { toString(): string; toLocaleString(): string; // ... rest ...}declare var Object: { new (value?: any): Object; (): any; (value: any): any; // ... rest ...}
I tried that and it works like charm.
Well an interface with a construct signature is not meant to be implemented by any class(at first glance this might look weird for guys with C#/Java background like me but give it a chance). It is slightly different.
For a moment think of it as a interface with a call signature(like a @FunctionalInterface in Java world). Its purpose is to describe a function type..kind of. The described signature is supposed to be satisfied by a function object...but not just any high level function or a method. It should be a function which knows how to construct an object, a function that gets called when new
keyword is used.
So an interface with a construct signature defines the signature of a constructor ! The constructor of your class that should comply with the signature defined in the interface(think of it as the constructor implements the interface). It is like a factory !
Here is a short snippet of code that tries to demonstrate the most common usage:
interface ClassicInterface { // old school interface like in C#/Java method1(); ... methodN();}interface Factory { //knows how to construct an object // NOTE: pay attention to the return type new (myNumberParam: number, myStringParam: string): ClassicInterface}class MyImplementation implements ClassicInterface { // The constructor looks like the signature described in Factory constructor(num: number, s: string) { } // obviously returns an instance of ClassicInterface method1() {} ... methodN() {}}class MyOtherImplementation implements ClassicInterface { // The constructor looks like the signature described in Factory constructor(n: number, s: string) { } // obviously returns an instance of ClassicInterface method1() {} ... methodN() {}}// And here is the polymorphism of constructionfunction instantiateClassicInterface(ctor: Factory, myNumberParam: number, myStringParam: string): ClassicInterface { return new ctor(myNumberParam, myStringParam);}// And this is how we do itlet iWantTheFirstImpl = instantiateClassicInterface(MyImplementation, 3.14, "smile");let iWantTheSecondImpl = instantiateClassicInterface(MyOtherImplementation, 42, "vafli");