Search code examples
typescriptdecoratormixins

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.


Solution

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