In JavaScript, I can define a constructor function which can be called with or without new:
function MyCl
TL;DR
If you are targeting ES6 and you really want to use class to store your data, not a function:
function that simply invokes your class constructor with its arguments;function's prototype to the prototype of your class.From now you are able to call that function either with or without new keyword to generate new class instances.
Typescript playground
Typescript provides an ability to create such a function (let's call it a "callable constructor") in a strongly typed way. Well, any type is necessary in intermediate type definitions (replacing it with unknown causes errors), but this fact will not affect your experience.
First of all we need to define basic types to describe entities we are working with:
// Let's assume "class X {}". X itself (it has type "typeof X") can be called with "new" keyword,
// thus "typeof X" extends this type
type Constructor = new(...args: Array) => any;
// Extracts argument types from class constructor
type ConstructorArgs =
TConstructor extends new(...args: infer TArgs) => any ? TArgs : never;
// Extracts class instance type from class constructor
type ConstructorClass =
TConstructor extends new(...args: Array) => infer TClass ? TClass : never;
// This is what we want: to be able to create new class instances
// either with or without "new" keyword
type CallableConstructor =
TConstructor & ((...args: ConstructorArgs) => ConstructorClass);
The next step is to write a function that accepts regular class constructors and creates corresponding "callable constructors".
function CreateCallableConstructor(
type: TConstructor
): CallableConstructor {
function createInstance(
...args: ConstructorArgs
): ConstructorClass {
return new type(...args);
}
createInstance.prototype = type.prototype;
return createInstance as CallableConstructor;
}
Now all we have to do is to create our "callable constructor" and check it really works.
class TestClass {
constructor(readonly property: number) { }
}
const CallableTestConstructor = CreateCallableConstructor(TestClass);
const viaCall = CallableTestConstructor(56) // inferred type is TestClass
console.log(viaCall instanceof TestClass) // true
console.log(viaCall.property) // 56
const viaNew = new CallableTestConstructor(123) // inferred type is TestClass
console.log(viaNew instanceof TestClass) // true
console.log(viaNew.property) // 123
CallableTestConstructor('wrong_arg'); // error
new CallableTestConstructor('wrong_arg'); // error