Versatile TypeScript decorator for various functions

Seeking to create a versatile decorator that can be applied to both classes and properties, I have encountered an issue with type definitions in my code. Here is the current implementation:

type Decorator<T> = T extends Function
  ? (target: T) => T
  : (target: T, propertyKey: string) => void;

function Schema<T>(): Decorator<T> {
  return (target: T, propertyKey?: string) => {
    if (target instanceof Function) {
      return target;
    }
  };
}

@Schema()
export default class MyClass {
  @Schema()
  bool: boolean;
}

Playground

Explicitly setting the generic T to typeof BooleanProperty resolves the issue. However, my goal is for Typescript to infer the type without any errors.

I have explored options like function overloading but have not found success. How can I address this problem?

Answer №1

If you want your decorator factory to be non-generic and produce a generic decorator, you'll need to make some changes. Currently, your decorator factory is generic, returning a non-generic decorator. Here's the distinction between the two scenarios:

// Incorrect
declare const genericFactoryForSpecificFunction: <T>() => (x: T) => T;
const oops = genericFactoryForSpecificFunction()(123); // unknown

// Correct
declare const specificFactoryForGenericFunction: () => <T>(x: T) => T;
const okay = specificFactoryForGenericFunction()(123); // 123

In the incorrect case, the compiler has trouble inferring T as there's no value of type T available in the expression where

genericFactoryForSpecificFunction()
is called. This results in an inferred type of unknown, leading to the return value also being unknown. In the correct case, inference happens at the call to the function returned by
specificFactoryForGenericFunction()
, allowing the compiler to use a value of type T for inference, resulting in 123 as output.


Once you implement this change, the Decorator type needs to represent a specific type that denotes a generic function call signature. You can achieve this using multi-call-signature overloads:

type Decorator = {
  <T extends new (...args: any) => any>(ctor: T): T; 
  <T, K extends keyof T>(proto: T, member: K): void; 
}

declare const d: Decorator;
d(RegExp); // okay, class decorator
d({ a: "" }, "a") // okay, prop decorator
d(RegExp, "oops"); // error, cannot pass prop to class decorator

Your updated example code will then look like this:

function Schema(): Decorator {
  return (target: any, propertyKey?: string) => {
    if (target instanceof Function) {
      return target;
    }
    return;
  };
}

@Schema()
export default class MyClass {
  @Schema()
  bool: boolean = false;
}

Everything seems to be in order now.


Hope this explanation helps. Best of luck with your implementation!

Access the playground link here

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

"Exploring the world of Angular, promises, and effective caching strategies in

In my Angular application, there is a service responsible for loading items from the server. When an item is selected in a view, the corresponding item should be retrieved from the list and displayed in detail. The issue arises when I reload the page – ...

Guide on integrating angular-schema-form into an Ionic 2.0 project using typescript

Recently, I embarked on creating an app with Ionic from scratch and decided to integrate the framework. While I faced no issues executing the example on a webpage, I encountered difficulties when attempting to do so with Ionic. To kickstart the project, ...

Angular 2 Calendar Implementation with PrimeNG Date in Epoch Time

I've encountered an issue with the PrimeNG calendar component when using time in milliseconds HTML <p-calendar [(ngModel)]="task.startDate" [minDate]="minDateValue" [maxDate]="maxDateValue" readonlyInput="readonlyInput" [showIcon] ...

Using Angular NgUpgrade to inject an AngularJS service into an Angular service results in an error message stating: Unhandled Promise rejection: Cannot read property 'get' of undefined; Zone:

I have noticed several similar issues on this platform, but none of the solutions seem to work for me. My understanding is that because our Ng2App is bootstrapped first, it does not have a reference to $injector yet. Consequently, when I attempt to use it ...

What type of event does the Input element in material-ui v1 listen for?

I'm currently grappling with material-ui v1 as I search for the appropriate event type for input elements. Take a look at the code snippet below: <Select value={this.numberOfTickets} onChange={this.setNumberOfTickets}> .... Here is the impleme ...

Changing Minute to HH:MM:SS in Angular 2

Instructions:- Minutes = 1220; Solution:- Time = 20:20:00; Is there a way to convert the minute value into a time format in Angular 2? ...

Contrary to GraphQLNonNull

I am currently working on implementing GraphQL and I have encountered a problem. Here is an example of the code I wrote for GraphQL: export const menuItemDataType = new GraphQL.GraphQLObjectType({ name: 'MenuItemData', fields: () => ...

What method can be used to seamlessly integrate Vue.js into a TypeScript file?

The focus here is on this particular file: import Vue from 'vue'; It's currently appearing in red within the IDE because the necessary steps to define 'vue' have not been completed yet. What is the best way to integrate without r ...

Navigate to the logout page upon encountering an error during the request

I recently upgraded our application from angular 2 to angular 5 and also made the switch from the deprecated Http module to the new HttpClient. In the previous version of the application, I used the Http-Client to redirect to a specific page in case of er ...

Turning XSD into TypeScript code

Stumbling upon this tool called CXSD, I was intrigued. The documentation describes cxsd as a streaming XSD parser and XML parser generator designed for Node.js and TypeScript (optional but highly recommended). It seemed like the perfect solution for my ne ...

Using Typescript to establish a connection between ngModel and an object's property

Let's talk about how we can dynamically bind an input to an undefined property in an object. For example, we have an object named user: let user = {}; How can we bind an input to a property that doesn't exist yet? Like this: <input [(ngMode ...

Creating a custom Map and Array declaration in a .d.ts file with Typescript

I am looking to define custom type declarations for the Map and Array classes in a .d.ts file like this: //customTypes.d.ts interface CustomMap<U, V> { get(key: U): V | undefined; set(key: U, val: V): void; } interface CustomArray<T> ...

Image not found in next.js

Working Environment ・ next.js ・ typescript ・ styled-components I uploaded the image in the folder inside pages, but it is not showing up. Why is that? // package.json   { "name": "nextapp", "version": &qu ...

Extension for VSCode: Retrieve previous and current versions of a file

My current project involves creating a VSCode extension that needs to access the current open file and the same file from the previous git revision/commit. This is essentially what happens when you click the open changes button in vscode. https://i.stack. ...

Customizing variables in React based on the environment

I am working on a React app that includes a chart component which calls an external API. When the app is running locally, the API URL is set to localhost:8080. However, when the app is deployed, the API URL needs to be changed to prod:8080. I have tried ...

Using TypeScript with forwardRef in React

I have been experimenting with using forwardRef in TypeScript React to access a child state. Despite following examples I found online, most are in JavaScript and I am encountering compiler errors. In an attempt to simplify the code, I encountered an issu ...

Obtain numeric sub-string from a string in typescript

Here is a sample string format: https://i.sstatic.net/SQ1ez.png https://i.sstatic.net/8Rg1C.png I need to extract the Revision number from the name. I attempted using Substring() but I require a universal solution that can retrieve the Revision number fr ...

If every single item in an array satisfies a specific condition

I am working with a structure that looks like this: { documentGroup: { Id: 000 Children: [ { Id: 000 Status: 1 }, { Id: 000 Status: 2 ...

Module 'bcryptjs' could not be located

Recently, I added the @types/bcryptjs package to my Node.js project. Initially, there were no issues with importing it. However, when I attempted to use it in my code by including the line: console.log(bcrypt.hashSync(req.body.password)) I encountered an ...

Javascript - Items inserted into array are not in the expected order

I've encountered a minor issue with adding elements in the correct place within an array. My goal is to insert the uppercase letters of the alphabet after the last lowercase letter 'z'. However, when implementing the code provided, I noticed ...