What are the best strategies for combining multiple TypeScript class decorators?

I have a set of unique class decorators that I apply to multiple classes. It looks something like this:

@awesome
@cool
@amazing
export class MySpecialClass { /* ..... */ }

However, since I use these three decorators across many classes, I want to condense them into one decorator like this:

@super
export class MySpecialClass { /* ... */ }

I attempted to create a new decorator that chains the calls like so:

export function super<ReturnType>(ctor: Constructor<ReturnType>) {
    return amazing(cool(awesome(ctor)));
}

According to the TypeScript documentation, applying multiple decorators should work similarly to function composition, which is why I thought chaining them would be possible. But when compiling (using TypeScript 1.8), I encountered an error message like this:

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 for me to create this 'wrapper' decorator to streamline my code?

Answer №1

After realizing where I went wrong while trying to present a more thorough version of my problem to @David, I have come to a solution.

Here is a complete 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 something to the constructor
    return Target;
}
function bar(Target: Constructor<B>): Constructor<B> { 
    // do something 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 issue from me. It seems I misinterpreted the compiler output.

The issue was with how I defined my decorators:

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

It should have been:

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

I observed that setting the return types in the decorators to any resolved the compile errors. The additional generic parameter allowed the type information to flow smoothly through the decorators. Otherwise, it seemed like Constructor<MyClass> could not be assigned an instance of Constructor<A> (since A lacked the other interfaces). Interestingly, I encountered more errors in the decorators when I added the declaration of d in MyClass.

In conclusion - exercise caution with generics when working with class decorators, or opt to return any.

Answer №2

Here is a possible way to format it:

function exportOne(target: any)
{
    return target;
}

function exportTwo(target: any)
{
    return target;
}

function exportThree(target: any)
{
    return target;
}

function standardFunction<T>() 
{
    return (target: { new(): T}) =>
    {
        return exportOne(exportTwo(exportThree(target)));
    }
}

@exportOne
@exportTwo
@exportThree
export class MyClassExample  { /* ... */ }

@standardFunction<MyClassExample>()
export class MyClassAExample { /* ... */ }

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Using TypeScript, add an observable to an array of observables using RxJS

Can someone explain how to include Observable<News> in an array of observables? newsArray: Observable<Array<any>>; addNews(newsId: number) { let news = this.newsService.getNewNews(newsId); // this results in an Observable<News> ...

The letter 'X' is not suitable for use as a JSX component because its return type 'Element[]' does not qualify as a valid JSX element

Currently, I am working on rendering two simple joke cards in TypeScript. The cards are displaying correctly in my browser, but I've encountered an error message that says: 'Jokes' cannot be used as a JSX component. Its return type 'Ele ...

There seems to be a console error in Angular 5 when using IE 11

I've developed an Angular 4 application using MVC and Visual Studio 2015. Everything runs smoothly when I access the application on Chrome, but I encounter the following exception on IE 11: XHR error: (404 Not Found) loading http://localhost/src/ma ...

When attempting to print a Rectangle on my webpage using JavaScript and taking user input, I encountered an error stating that 'str' is not defined

I'm trying to implement a feature where clicking on the "try it" button will prompt the user for the number of lines and then print that many lines in the form of a rectangle. However, when I run my code, nothing appears on the DOM and there is an err ...

Leveraging the power of React's callback ref in conjunction with a

I'm currently working on updating our Checkbox react component to support the indeterminate state while also making sure it properly forwards refs. The existing checkbox component already uses a callback ref internally to handle the indeterminate prop ...

Steer clear of using inline styling when designing with Mui V5

I firmly believe that separating styling from code enhances the clarity and cleanliness of the code. Personally, I have always viewed using inline styling (style={{}}) as a bad practice. In Mui V4, it was simple - I would create a styles file and import i ...

Can you explain the step-by-step process of how an await/async program runs in TypeScript/JavaScript or Python?

As a C++ developer specializing in multithreading, I've been diving into the intricacies of async/await. It's been a challenge for me as these concepts differ from how C++ programs are typically executed. I grasp the concept of Promise objects, ...

I would appreciate it if someone could explain the significance of the image display area

Please add a description for this image The table presented below outlines different declaration types: Declaration Type Namespace Type Value Namespace X X Class X X Enum X X Interface X Type Alias X Function X Variable X A ...

Setting angular variables by assigning form values

My current reactive form setup looks like this: this.editform = new FormGroup({ 'username' : new FormControl(null,[Validators.required]), 'password' : new FormControl(null,[Validators.required]), 'full_name' : ne ...

Why did my compilation process fail to include the style files despite compiling all other files successfully?

As English is not my first language, I kindly ask for your understanding with any typing mistakes. I have created a workspace with the image depicted here; Afterwards, I executed "tsc -p ." to compile my files; You can view the generated files here Unf ...

Incorrect typings being output by rxjs map

combineLatest([of(1), of('test')]).pipe( map(([myNumber, myString]) => { return [myNumber, myString]; }), map(([myNewNumber, myNewString]) => { const test = myNewString.length; }) ); Property 'length' does not ...

What is the reason TypeScript struggles to automatically deduce assignments of identical object types?

Imagine a scenario with a simple code snippet to illustrate the issue: interface I { n?: number; s?: string; } const a: I = { n: 1, } const b: I = { n: 2, s: 'b', } const props = ['n', 's'] as const; for (const p ...

Encountering a problem with lazy loading of module routing paths. Issue arises when attempting to navigate to http://localhost:4200

AppLazyLoadingMoudle import {NgModule} from '@angular/core'; import {RouterModule, Routes} from '@angular/router'; const ROUTES : Routes = [ /* ProductModule (defined in the feature module) is loaded lazily when navigating ...

Refreshing Form in Angular 2

When I remove a form control from my form, it causes the form to always be invalid. However, if I delete a character from another input field and then add the same character back in (to trigger a change event), the form becomes valid as expected. Is ther ...

Unclear error message when implementing union types in TypeScript

Currently, I am attempting to define a union type for a value in Firestore: interface StringValue { stringValue: string; } interface BooleanValue { booleanValue: boolean; } type ValueType = StringValue | BooleanValue; var value: ValueType = { bo ...

Issue: Failed to access the 'setDir' property of an undefined object

Greetings, I am a newcomer to Ionic/Angular and have been facing a particular issue for over 20 days now. I have created a small app and would like to implement multi-language support with both RTL and LTR directions. I followed the documentation provided ...

Aggregate the values in an array and organize them into an object based on their frequency

I have an array of information structured like this: 0: { first: "sea", second: "deniz", languageId: "English-Turkish"} 1: { first: "play", second: "oynamak", languageId: "English-Turkish&qu ...

React: Why aren't class methods always running as expected?

I created a class component that retrieves a list of applications from an external API. It then sends a separate request for each application to check its status. The fetching of the applications works well, but there is an issue with the pinging process ...

Troubleshooting the issue of having multiple menu items in Material UI

Every time I attempt to add the Menu component multiple times, I encounter an issue with the popup list displaying incorrectly. To demonstrate this problem, you can view it through the link provided on codesandbox below. I have included data-id attributes ...

Attempting to perform an API invocation on a distant endpoint utilizing NestJS

var unirest = require("unirest"); var req = unirest("GET", "https://edamam-edamam-nutrition-analysis.p.rapidapi.com/api/nutrition-data"); req.query({ "ingr": "1 large apple" }); req.headers({ &qu ...