1

Looking for ways to instantiate classes without new in typescript, this fine answer https://stackoverflow.com/a/44061744/430531 works nicely in javascript.

The code in the repo referred https://github.com/digital-flowers/classy-decorator is a simple class decorator:

var classy = function classy() {
    return function (Class) {
        var _Class = function _Class() {
            for (var _len = arguments.length, rest = Array(_len), _key = 0; _key < _len; _key++) {
                rest[_key] = arguments[_key];
            }

            return new(Function.prototype.bind.apply(Class, [null].concat(rest)))();
        };
        _Class.prototype = Class.prototype;
        return _Class;
    };
};

But using it under Typescript like this:

@classy()
class Someone{
    constructor(private name: string){}
}
debugger
let some1 = new Someone("john");
let some2 = Someone("joe")  // some2 is also an instance of Someone! but typescript complains that it's not callable

I have yet to dig into the function decorator itself to understand it well and that also has ts errors but is operational.

But I wonder if there's a way to avoid the Typescript error when instancing without new (let some2 = Someone("joe"))

tru7
  • 6,348
  • 5
  • 35
  • 59
  • Decorators don't mutate types in TypeScript, see [ms/TS#4881](https://github.com/microsoft/TypeScript/issues/4881), so while you can apply a decorator to `Someone`, it doesn't change the type of `Someone`. The workaround here is usually to just call your decorator like a function. [Here is an example](https://tsplay.dev/mpnaBw). Does that fully address your question? If so I'll write up an answer; if not, what am I missing? – jcalz Jul 26 '22 at 15:17

1 Answers1

1

Unfortunately, decorators don't mutate the types of the things they decorate in TypeScript. That means the Someone constructor will not be seen by the compiler to have a call signature. There is a longstanding feature request at microsoft/TypeScript#4881 to support class decorator mutation at the type level, but it's been stuck in limbo for a long time waiting for the proposal for adding decorators to JavaScript to be approved. It's now at Stage 3 of the TC39 process, and microsoft/TypeScript#48885 is tracking implementing JS conformant decorators in TypeScript, but I don't know when or if type mutation will be implemented. For now, this is just not possible.


The main workaround here is to calling the decorator as a regular function, since function outputs are able to be different in type from their inputs. For example, we could make a tsClassy function which we assert to behave as something that takes a constructor as input and returns a callable constructor as output:

const tsClassy = classy() as
  <T, A extends any[], R>(ctor: T & (new (...args: A) => R)) => T & { (...args: A): R };

In order to use it, we can't just decorate Someone. Instead we must call it on a class constructor and save the output to its own variable. Perhaps like this:

const Someone = tsClassy(class Someone {
  constructor(public name: string) { } 
});
//const Someone: typeof Someone & ((name: string) => Someone)

Now the compiler sees Someone both as a class constructor and as a callable function:

let some1 = new Someone("john");
// let some1: Someone
console.log(some1.name) // john 

let some2 = Someone("joe");
//let some2: Someone
console.log(some2.name) // joe

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360