Constructor Function Type for Class Decorators

259 Views Asked by At

I have a class decorator where I want to pass type for a constructor function parameter to avoid linting error,so I can't pass type as Function.For Example-

function Student(config) { 
 return function (target:Function) {
 target.prototype["tValue"]="some value"


 }
 }   

@Student({
course: "Angular 2"
})
class Person {
} 

@Student({
course: "Angular 15"
})
class People {

} 

So here we have a class decorator Student which gets attached to 2 classes Person and People.What should I pass the type of target in in decorator? I tried Function but that gives linting error.This Student decorator can be applied to multiple classes of different types.DO we have some generic type for this?

2

There are 2 best solutions below

1
On

You can suppress this by adding the following in compilerOptions in ts.config file

    "esModuleInterop": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
0
On

Since 5.0 TypeScript provides an implementation of the official decorators proposal drafted by TC39. To enable experimentation and framework support, previous versions of popular transpilers such as Babel and TypeScript provided decorator support based on a now defunct proposal. While said functionality isn't not technically deprecated, their use is to be discouraged as they will never be adopted by JavaScript and have non-trivial differences.

The following solution uses the current the JavaScript decorators proposal stage 3.

the following typechecks and is composable with other decorators:

function Student(config: {course: string}) {
  console.log('creating configured decorator config:');
  console.log(config);

  return function <C extends abstract new (...args: any[]) => any>(
    target: C,
    context: ClassDecoratorContext<C>
  ) {
    target.prototype.tValue = 'some value';
    console.table(Object.entries(context));
    return target;
  };
}

Playground Link

Looking at the definition above, we notice a few things right off the bat. Firstly, the Student decorator is actually a decorator factory. That is to say, it takes some configuration, here config: {course: string} and returns a decorator which is then to be applied to our classes. The returned function is where things get interesting.

Now, onto the decorator returned from the Student decorator factory,the focal point of your question.

The outer function Student is just a function that returns a customized decorator when called. As a class decorator (as apposed to a class member decorator) it takes two parameters target and context. When the decorator is applied to a class that class is passed as for the target parameter and context provides an API which can be used to examine the decorated class and customize the class to which the decorator is applied. Note we are talking about the class object itself, not what it creates when newed.

In TypeScript, we typically write the type for a class as follows.

abstract new (...args: any[]) => any

Here, we use a type parameter, C, and constrain it by the above universal constructor signature, bringing us to

function<C extends abstract new (...args: any[]) => any>(target: C)

Here the decorator is rendered type compatible to any class, and the type of that class itself is captured by C.

Now on to the context parameter. Its type is ClassDecoratorContext<T>, a generic type provided as part of TypeScript's description of the JavaScript standard libraries and can be found in lib.decorators.d.ts.

ClassDecoratorContext<C>

Notice how we are passing along the type parameter C, instantiating the ClassDecoratorContext with it. This leaves us with the following function signature:

function<C extends abstract new (...args: any[]) => any>(
  target: C,
  context: ClassDecoratorContext<C>
)

Both parameters will be supplied when the class is decorated.

This establishes a correlation between the parameters and provides for a strongly typed context whose API matches target. Now, one might be wondering why we both with strongly typing our parameters since we don't make use of the type information. In many cases, it can be useful to write a decorator that constrains its parameter restrictively.

Consider:

const personConstructors: Array<new (...args: any[]) => Person> = [];
const people: Array<Person> = [];

export function registeredConstructor<C extends new (...args: []) => Person>(
  target: C,
  context: ClassDecoratorContext<C>
) {
  console.log(`Decorating class ${target.name}`);
  context.addInitializer(function () {
    personConstructors.push(this);

    const person = new this();
    person.firstName = 'Phillip';
    person.lastName = 'Pullman';
    if (person instanceof Spouse) {
      const partner = people.find(
        (p): p is Spouse => p instanceof Spouse && p.spouse === person
      );
      person.spouse = partner;
    }

    people.push(person);
  });
}

@registeredConstructor
class Person {
  accessor firstName: string | undefined;
  accessor lastName: string | undefined;
}

@registeredConstructor
class Spouse extends Person {
  spouse?: Spouse;
}

Playground Link