Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 928
ComradeCow Avatar asked Aug 18 '16 19:08

ComradeCow


Video Answer


1 Answers

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.

like image 105
ComradeCow Avatar answered Sep 30 '22 18:09

ComradeCow