简体   繁体   中英

How to add an optional parameter to a decorator in TypeScript?

I want to create a field decorator that optionally can take an argument. The argument should contain any of the following values: nothing, a boolean or a function. I know how to do this, but I'm not 100% happy with the result:

export class TestClass{

   @Required(isRequired) 
   public testField: string;

}

export function isRequired():boolean{
   ... some validation logic, maybe depending on other fields...
   return result;
}

Implementation of @Required:

export function Required(expression?: boolean|Function): Function {
    return (target: any, key: string) => {
        if (expression === null || typeof expression == 'undefined') {
            expression = true;
        }
        console.log("Required found: " + expression, ":", target, key);
        ... register the field and its validation expression for later usage
    }
}

So this works fine, but when I do not want to add an expression (and thus use the defaulted "true" expression) I want to be able to write it like this:

class TestClass{

   @Required
   public testField: string;

}

I get a TypeScript error (TS1240) saying:

Unable to resolve signature of property decorator when called as an expression. Supplied parameters do not match any signature of call target

So I need to write @Required()

class TestClass{

   @Required()
   public testField: string;

}

Is it possible to write a decorator implementation that takes optionally an argument and when that argument is not specified there is no need to add the "()" ?

Actually, it is possible.

Here is a working example:

export type Target = {
  new (...args: any[]): any,
  name: string
};

export function Component(target: Target): void;
export function Component(name: string): (target: Target) => void;
export function Component(nameOrTarget: string | Target) {
  if (typeof nameOrTarget !== 'string') {
    console.log(nameOrTarget.name, ' is now decorated');
  } else {
    return function (target: Target) {
      const name = nameOrTarget || target.name;
      console.log(name, ' is now decorated');
    };
  }
}

@Component
export class MyDatabase { }

@Component('Hello Db')
export class MyHelloDatabase { }

The most important part is the following two lines:

export function Component(target: Target): void;
export function Component(name: string): (target: Target) => void;

If anyone is looking for more information, check out this GitHub issue .

No, you probably can't do that.
The reason for that is that decorators have a specific signature (which varies depending on the type of the decorator).
If you use a decorator function then you don't need the brackets, but if you use a decorator factory (like you do in your example) then you must call it using brackets.

What you can do is separating the two into two different functions:

function Required(target: any, key: string, expression?: boolean | Function) {
    if (expression === null || typeof expression == 'undefined') {
        expression = true;
    }
    console.log("Required found: " + expression, ":", target, key);
}

function RequiredWith(expression: boolean | Function): Function {
    return (target: any, key: string) => {
        return Required(target, key, expression);
    }
}

And then you can either:

class TestClass {
    @Required
    public testField: string;
}

Or:

class TestClass2 {
    @RequiredWith(true)
    public testField: string;
}

( code in playground )

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM