Reputation: 6844
Let's say I have a class decorator that adds some method to a class.
function decorator(target) {
target.prototype.method = function() {};
}
@decorator
class Test {
constructor() {
this.method() <------- Property method does not exists on type Test
}
}
If I have the ability to add decorators but typescript does not recognize them, it's worth nothing.
There is a way to solve this problem?
Upvotes: 3
Views: 1365
Reputation: 7316
TL;DR — you are right. Both in that they don't recognize properties and that they should.
As at Oct 2023, TypeScript's implementation of class decorators is incomplete at best.
The docs specifically state (although very quietly) that class decorators do not modify the type of the class they are applied to. This can be confirmed by experiment. It is very odd because, in js, decorators replace the target with whatever they return, which could be an object of almost any type.
Without being able to affect the type information of their target class, decorators in typescript are a pretty awkward non-feature at the moment. Almost all reasonable uses of annotation add or mutate properties on a class, return a subtype, or otherwise do something that changes typing. At the moment, the best you can do is make weird footguns.
There is some hearty discussion about this on a github issue.
My personal opinion is that class decorators are not finished and should return to experimental status. Function decorators seem more finished/useful.
Upvotes: 1
Reputation: 250862
Decorators are used for all kinds of reasons, from modifying a class, to aspect-oriented programming, to simple logging. Almost anything can happen inside a decorator. At this time, the contents of the decorator is not used to modify the type information for the class (in some cases, it may not ever be possible to do so, although in your case it would be possible as it is such a straightforward one).
If you want to add methods to a class, you might consider TypeScript mixins as an alternative, or simple inheritance (see below).
A simple fix for your issue would be to provide an empty method to generate the type information you want:
@decorator
class Test {
constructor() {
this.method();
}
method(): void { }
}
An alternate solution would be to replace the constructor within the decorator - so you add the method, and the constructor call to the method, within the decorator - thus ensuring that the implementation will be there.
function decorator(target: any) {
const original = target;
const constr: any = (...args) => {
const c: any = () => {
return original.apply(null, args);
}
c.prototype = original.prototype;
c.prototype.method = function () { alert('method');};
const inst = new c();
inst.method();
return inst;
}
constr.prototype = original.prototype;
return constr;
}
@decorator
class Test {
constructor() {
}
}
const test = new Test();
This is the boring, but often correct solution (and if you don't want to inherit, you could delegate instead):
class HasMethod {
method() {
alert('Method');
}
}
class Test extends HasMethod {
constructor() {
super();
this.method();
}
}
const test = new Test();
Upvotes: 3