Forseti
Forseti

Reputation: 2945

Adding properties to a class via decorators in TypeScript

On the TypeScript's Decorator reference page there is a code snipped that illustrates how to override the constructor with class decorator:

function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
    return class extends constructor {
        newProperty = "new property";
        hello = "override";
    }
}

@classDecorator
class Greeter {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
}

console.log(new Greeter("world"));

and in logs:

class_1 {
  property: 'property',
  hello: 'override',
  newProperty: 'new property' }

So far so good. BUT trying to access newProperty by dot notation fails with:

Property 'newProperty' does not exist on type 'Greeter'.ts(2339)

error and it's not listed in hints in VS Code. One can access it by bracket notation but TS warns that

Element implicitly has an 'any' type because type 'Greeter' has no index signature.ts(7017)

Am I missing something? How to implement adding new properties via Decorators in type-safe way? I'd like to have normal compiler support just like with regular class members.

Upvotes: 18

Views: 17719

Answers (3)

MrBar
MrBar

Reputation: 1108

Not a direct solution but a workaround that avoids the limiation of decorators: In some cases it's possible to replace the decorator with a plain class that will properly inherit types.

class ClassDecorator {
        newProperty = "new property";
        hello = "override";
}

class Greeter extends ClassDecorator {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
}

console.log(new Greeter("world"));

Upvotes: -3

blackmiaool
blackmiaool

Reputation: 5344

function classDecorator<T extends { new(...args: any[]): {} }>(constructor: T) {
    return class extends constructor {
        newProperty = "new property";
        hello = "override";
    }
}
interface classInterface {
    newProperty: string;
    hello: string;
}

//trick
interface Greeter extends classInterface { };

@classDecorator
class Greeter {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
}
const b = new Greeter();
console.log(b.newProperty);

Seems we can use interface trick to solve the problem. Reference of the trick: https://stackoverflow.com/a/52373394/4831179

Upvotes: 9

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249506

Decorators by design can't change the type of a class. This is stil in discussion and it appears until the decorator proposal is finalized the team will not change the behavior. You can use mixins for this task (read about mixins in ts)

Using mixins the code would look something like:

function classDecorator<T extends { new(...args: any[]): {} }>(constructor: T) {
    return class extends constructor {
        newProperty = "new property";
        hello = "override";
    }
}

const Greeter = classDecorator(class {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
});
type Greeter = InstanceType<typeof Greeter> // have the instance type just as if we were to declare a class

console.log(new Greeter("world").newProperty);

Upvotes: 23

Related Questions