undefined
undefined

Reputation: 6844

TypeScript decorators does not recognize the new properties

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

Answers (2)

aaaidan
aaaidan

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

Fenton
Fenton

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).

Placeholder Implementation

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 { }
}

Replace Constructor

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();

Inheritance

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

Related Questions