Alexey Savchuk
Alexey Savchuk

Reputation: 1128

TypeScript class decorators - add class method

How to define property with TypeScript and decorators?

For example I have this class decorator:

function Entity<TFunction extends Function>(target: TFunction): TFunction {
    Object.defineProperty(target.prototype, 'test', {
        value: function() {
            console.log('test call');
            return 'test result';
        }
    });
    return target;
}

And use it:

@Entity
class Project {
    //
}

let project = new Project();
console.log(project.test());

I have this console log:

test call            entity.ts:5
test result          entity.ts:18

This code correctly worked, but tsc return error:

entity.ts(18,21): error TS2339: Property 'test' does not exist on type 'Project'.

How to fix this error?

Upvotes: 13

Views: 11540

Answers (2)

Dovakeidy
Dovakeidy

Reputation: 35

I was able to make it work with the combination of decorator and mixin.

Firstly, you need to defined your mixin.

NOTE : The use of mixin is not mandatory, but the mixin pattern allow multiple inheritance and more flexibility

export function YourMixin<T extends YourConstructor>({ Base }: MixinProps<T>) {

  class Mixin extends Base {

    // You can define other decorators here
    async method() {
      // your logic
    }

  }

  return Mixin;
}

// Define your constructor type.
export type Constructor<T, Arguments extends unknown[] = any[]> = new (...arguments_: Arguments) => T;
export type YourConstructor<T = { }> = Constructor<T>;

// Define your interface with the same name of your decorator to enable declaration merging.
export interface YourDecorator { 
  async method(): Promise<void>;
}

// Define your decorator
export function YourDecorator(options = {}) {

  return function <T extends YourConstructor>(Base: T) {

    // You can define other decorators here
    class YourClass extends YourMixin(Base) { }

    // Apply original class descriptors to the new class
    const ownPropertyDescriptors = Object.getOwnPropertyDescriptors(Base);

    const { prototype, ...descriptors } = ownPropertyDescriptors;

    Object.defineProperties(YourClass, descriptors);

    return YourClass as T;

  }

}

// Use your decorator and define your class interface that extends the decorator interface
// for declaration merging types.
interface Service extends YourDecorator {}
@YourDecorator({})
class Service {

}

Upvotes: 0

Amid
Amid

Reputation: 22322

Af far as I know for now this is not possible. There is a discussion on this issue here: issue.

So you either do not use decorators to extend classes, and maybe if appropriate use interfaces with declaration merging to extend type declaration. Or use type assertion (but loosing type checks): (<any>project).test()

Upvotes: 9

Related Questions