Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 710
Forseti Avatar asked Feb 21 '19 17:02

Forseti


2 Answers

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);
like image 80
Titian Cernicova-Dragomir Avatar answered Nov 17 '22 11:11

Titian Cernicova-Dragomir


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

like image 5
blackmiaool Avatar answered Nov 17 '22 10:11

blackmiaool