Search code examples
typescripttypescript-decorator

How to compose multiple TypeScript class decorators?


I have a family of class decorators that I repeat over many classes. Something similar to this:

@foo
@bar
@baz
export class MyClass { /* ..... */ }

Since I'm using those three decorators across multiple classes, I'd really like to break that down into one decorator, like so:

@standard
export class MyClass { /* ... */ }

I've tried to create a new class decorator that chains the decorator calls like this:

export function standard<ReturnType>(ctor: Constructor<ReturnType>) {
    return baz(bar(foo(ctor)));
}

The TypeScript handbook says applying multiple decorators should evaluate similar to function composition, which is why I figured I should be able to chain them together. However, come compile time (using TypeScript 1.8) I get an error similar to

Unable to resolve signature of class decorator when called as an expression. Type 'Constructor<ReturnType>' is not assignable to type 'void'.

Is there a way I can construct this 'wrapper' decorator to simplify my code?


Solution

  • In an attempt to build a more complete version of my problem for @David, I figured out where I was going wrong.

    A more, full example:

    interface Constructor<T> { new(...args: any[]): T }
    interface A { a: number; }
    interface B { b: number; }
    interface C { c: number; }
    
    function foo(Target: Constructor<A>): Constructor<A>{ 
        // do some stuff to the constructor
        return Target;
    }
    function bar(Target: Constructor<B>): Constructor<B> { 
        // do some stuff to the constructor
        return Target;
    }
    function baz(Target: Constructor<C>): Constructor<C> {
        // ....
        return Target;
    }
    
    function standard(ctor: Constructor<A & B & C>): Constructor<A & B & C> {
        return baz(bar(foo(ctor)));
    }
    
    @foo
    @bar
    @baz
    class MyClass implements A, B, C { a=1;b=2;c=3;d=6; }
    

    There was some implicit typing in my actual code that somewhat hid the problem from me. And apparently I couldn't read compiler output correctly.

    The problem was with how I declared my decorators:

    function foo(Target: Constructor<A>): Constructor<A> { }
    

    Needed to be

    function foo<T extends A>(Target: Constructor<T>): Constructor<T> {}
    

    I noticed if I set the return types in the decorators to any the compile errors went away. The extra generic parameter let the type info cleanly flow through the decorators. Otherwise I believe it saw (essentially) that Constructor<MyClass> could not be assigned an instance of Constructor<A> (since A is missing the other interfaces). Also curiously, I got more errors in the decorators to pop up if I added that declaration of d in MyClass.

    So in closing - when working with class decorators, be careful with your generics, or just return any.