使用类装饰器将装饰器添加到新属性

时间:2019-01-17 13:49:18

标签: angular typescript

我想知道的几乎全部都在标题中。 我想知道如何使用类装饰器添加带有其自身装饰器的新属性。

我想创建一个类装饰器Api(string []),它公开了类装饰器中列出的类方法。 为此,我想使用Angular的EventEmitter发出事件,为此我必须将@Output()装饰器添加到新属性。

我可以吗?

下面举一个例子:我只有MyClass,其方法为process,open,close。 我想创建装饰器以公开我想要的任何方法(在此打开和关闭)。我想象过一个添加了api属性的类装饰器和一个方法装饰器来公开一个方法吗?

class MyClass {
  @Output() api = new EventEmitter();

  $exposedMethods: object = {};

  constructor() {
    this.$exposedMethods = {
      open: this.open.bind(this),
      close: this.close.bind(this)
    };
    this.api.emit(this.$exposedMethods);
  }

  process() {

  }

  open() {
    // stuff...
  }

  close() {
    // stuff...
  }
}

1 个答案:

答案 0 :(得分:2)

好的,请做好准备,因为这是一个很难理解的概念

您在这里有现场演示:

https://stackblitz.com/edit/angular-2kxtzs?file=src%2Fapp%2Fhello.component.ts

对于代码:

import { Component, Input, Output, EventEmitter } from '@angular/core';

const Expose: (methods: string[]) => ClassDecorator = (methods) => {

  return component => {

    for (const method of methods) {
      const eventEmitterName = `${method}Emitter`;
      component.prototype[eventEmitterName] = new EventEmitter();

      const outputFactory = Output(method);

      const orgFn = component.prototype[method];

      component.prototype[method] = (...args) => {
        orgFn(...args);
        component.prototype[eventEmitterName].emit();
      }

      outputFactory(component.prototype, eventEmitterName);
    }
  }
}

@Component({
  selector: 'hello',
  template: `<button (click)="open()">Emit an open event</button>`,
  styles: [`h1 { font-family: Lato; }`]
})
@Expose(['open'])
export class HelloComponent {

  @Input() name: string;

  open() {
    console.log('Clicked on the button, now emitting an event');
  }

  ngOnInit() {}
}

类装饰器是函数。
在您的情况下,这是一个类装饰器工厂:您提供参数,并且它应该返回一个类装饰器。这是您可以看到的签名:

const Expose: (methods: string[]) => ClassDecorator = (methods) => { ... }

它声明Expose是一个返回Class Decorator的工厂。您的工厂接受方法列表作为参数。

现在,该工厂需要返回一个类装饰器。类装饰器是一个具有组件本身作为唯一参数的函数。这是线

return component => { ... }

它返回符合ClassDecorator签名的函数。

之后,您需要重写每个方法。因此,您将通过一个简单的循环遍历它们。

在循环中,我们将创建一个新的事件发射器。为简单起见,我们将使用名称[method]Emitter。因此,我们首先创建一个圣名:

const eventEmitterName = `${method}Emitter`;

完成后,我们将其绑定到组件的原型:

component.prototype[eventEmitterName] = new EventEmitter();

您现在有了事件发射器。

在那之后,您将需要将输出装饰器绑定到它。如果按照第一步进行操作,您将了解Output实际上也是工厂。这意味着它将返回一个签名为

MethodDecorator函数
(component, methodKey) => { ... }

(还有第三个参数,称为描述符,但是您不需要它,因此我将忽略它)。

一旦知道了这一点,我们就会为我们的方法获取工厂结果:

const outputFactory = Output(method);

这将创建一个以您的方法命名的输出(此处为open)。

一旦完成,我们将覆盖给定的方法以在处理完成时发出事件。

这是基本的JS函数重写:

const orgFn = component.prototype[method];

component.prototype[method] = (...args) => {
  orgFn(...args);
  component.prototype[eventEmitterName].emit();
}

在最后一行,我们通过先前创建的事件发射器发射事件。

现在,我们要做的就是将该事件发射器绑定到我们的组件。为此,我们只需调用输出工厂创建的方法装饰器即可。

outputFactory(component.prototype, eventEmitterName);

现在,您的装饰器已经完成并且可以工作。正如您在stackblitz上看到的那样,正在运行open函数中的代码,然后运行该代码,然后运行应用程序组件模板中的(open)输出的代码。

还有Voilààààà!