angular - 使用类装饰器将装饰器添加到新属性
问题描述
我想知道的几乎都在标题中。我想知道如何使用类装饰器添加具有自己的装饰器的新属性。
我想创建一个类装饰器 Api(string[]) 来公开类装饰器中列出的类方法。为此,我想EventEmitter
从 Angular 发出一个事件,为此我必须将@Output()
装饰器添加到一个新属性中。
我可以吗?
举个例子:我只有 MyClass 方法处理,打开,关闭。我想创建装饰器来公开我想要的任何方法(在这里,打开和关闭)。我想象了一个类装饰器,它添加了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...
}
}
解决方案
好的,所以要振作起来,因为这是一个很难掌握的概念。
你在这里有现场演示:
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
为返回类装饰器的工厂。您的工厂接受方法列表作为参数。
现在,这个工厂需要返回一个类装饰器。类装饰器是一个将组件本身作为唯一参数的函数。这是行
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)
应用程序组件模板中的输出代码就会运行。
瞧瞧!
推荐阅读
- react-native - FlatList 没有渲染它显示除 FlatList 之外的任何内容
- javascript - 如何在展开可折叠 div 时调用 API 并使用 AngularJS 用 API 响应填充其内容?
- laravel - 使用 json_decode 时为 foreach() 提供的参数无效
- c# - 使用带有 Pipedream SSE API 的 ServiceStack ServerEventsClient 时出现 404 未找到错误
- c++ - 使用自定义比较器定义映射,其中值数据结构也具有自定义比较器
- ruby-on-rails - 根据rails中另一个下拉列表的值生成下拉列表值
- json - 从 json api 角度获取对象中的对象不显示
- java - 如何使用 JavaFX 从树视图中的绝对路径中获取文件夹或文件名?
- c++ - 在 unordered_map 中使用浮点数作为键的好方法
- reactjs - 如何在反应日期选择器中动态禁用天数