【发布时间】:2016-08-18 15:47:35
【问题描述】:
我有几个组件装饰器声明,我在每个组件上重复,例如:
@Component({
moduleId: module.id,
directives: [BootstrapInputDirective]
})
如何将这些声明应用于我的所有组件?我试图用这个装饰器创建一个基类并用它扩展其他类,但基类装饰似乎不适用于派生类。
【问题讨论】:
标签: typescript angular
我有几个组件装饰器声明,我在每个组件上重复,例如:
@Component({
moduleId: module.id,
directives: [BootstrapInputDirective]
})
如何将这些声明应用于我的所有组件?我试图用这个装饰器创建一个基类并用它扩展其他类,但基类装饰似乎不适用于派生类。
【问题讨论】:
标签: typescript angular
@Component 是一个装饰器。这意味着它通过利用反射元数据库添加一些元数据数据来处理它应用的类。 Angular2 不会在父类上查找元数据。因此,不能在父类上使用装饰器。
关于BootstrapInputDirective 指令,您可以将其定义为平台指令。这样您就不需要每次都将它包含在组件的 directives 属性中。
这是一个示例:
(...)
import {PLATFORM_DIRECTIVES} from 'angular2/core';
bootstrap(AppComponent, [
provide(PLATFORM_DIRECTIVES, {useValue: [BootstrapInputDirective], multi:true})
]);
编辑
是的,您可以创建自己的装饰器来实现这一点。这是一个示例:
export function CustomComponent(annotation: any) {
return function (target: Function) {
var parentTarget = annotation.parent;
delete annotation.parent;
var parentAnnotations = Reflect.getMetadata('annotations', parentTarget);
var parentAnnotation = parentAnnotations[0];
Object.keys(parentAnnotation).forEach(key => {
if (isPresent(parentAnnotation[key])) {
annotation[key] = parentAnnotation[key];
}
});
var metadata = new ComponentMetadata(annotation);
Reflect.defineMetadata('annotations', [ metadata ], target);
}
}
CustomComponent 装饰器将以这种方式使用:
@Component({
template: `
<div>Test</div>
`
})
export class AbstractComponent {
}
@CustomComponent({
selector: 'sub',
parent: AbstractComponent
})
export class SubComponent extends AbstractComponent {
}
请注意,我们需要提供父类作为装饰器的输入,因为我们可以在装饰器中找到这个父类。反射元数据只将这个类的原型和元数据应用到类而不是关联的原型上。
编辑2
感谢 Nitzam 的回答,这是一个改进:
export function CustomComponent(annotation: any) {
return function (target: Function) {
var parentTarget = Object.getPrototypeOf(target.prototype).constructor;
var parentAnnotations = Reflect.getMetadata('annotations', parentTarget);
var parentAnnotation = parentAnnotations[0];
Object.keys(parentAnnotation).forEach(key => {
if (isPresent(parentAnnotation[key])) {
annotation[key] = parentAnnotation[key];
}
});
var metadata = new ComponentMetadata(annotation);
Reflect.defineMetadata('annotations', [ metadata ], target);
}
}
不需要parent 属性来引用自定义装饰器中的父类。
看到这个 plunkr:https://plnkr.co/edit/ks1iK41sIBFlYDb4aTHG?p=preview。
看到这个问题:
【讨论】:
CHECK:。基本上尝试的是permiter覆盖注释属性并可以访问组件基础的属性以合并到子组件语句中(失败=/)
function,以及function 是否运行基值作为参数。正确的?我相信还有一些调整,例如:多继承级别(N 级别),在同一上下文中注入多个级别的指令,合并属性中的分辨率等。可以在 this issue of Angular2 project? 中提出类似的建议?跨度>
您可以在引导函数中全局提供服务,例如:
bootstrap(AppComponent, [HTTP_PROVIDERS, provide(SharedService, {useValue: sharedService})]);
其中 sharedService 是您导入的服务。
【讨论】:
在最新版本的 Angular 之后,ComponentMetadata 类不可用,正如这里的少数成员指出的那样。
这就是我实现 CustomComponent 以使其工作的方式:
export function CustomComponent(annotation: any) {
return function (target: Function) {
let parentTarget = Object.getPrototypeOf(target.prototype).constructor;
let parentAnnotations = Reflect.getOwnMetadata('annotations', parentTarget);
let parentAnnotation = parentAnnotations[0];
Object.keys(annotation).forEach(key => {
parentAnnotation[key] = annotation[key];
});
};
}
希望对你有帮助!
编辑: 前面的代码块,即使有效,它也会覆盖扩展类的原始元数据。在下面找到它的增强版本,允许您在不修改基类的情况下进行多重继承和覆盖。
export function ExtendComponent(annotation: any) {
return function (target: Function) {
let currentTarget = target.prototype.constructor;
let parentTarget = Object.getPrototypeOf(target.prototype).constructor;
let parentAnnotations = Reflect.getOwnMetadata('annotations', parentTarget);
Reflect.defineMetadata('annotations', [Object.create(parentAnnotations[0])], currentTarget);
let currentAnnotations = Reflect.getOwnMetadata('annotations', currentTarget);
Object.keys(annotation).forEach(key => {
currentAnnotations[0][key] = annotation[key];
});
};
}
【讨论】:
如果有人正在寻找更新的解决方案,Thierry Templier 的回答非常完美。除了 ComponentMetadata 已被弃用。使用 Component 代替对我有用。
完整的自定义装饰器CustomDecorator.ts 文件如下所示:
import 'zone.js';
import 'reflect-metadata';
import { Component } from '@angular/core';
import { isPresent } from "@angular/platform-browser/src/facade/lang";
export function CustomComponent(annotation: any) {
return function (target: Function) {
var parentTarget = Object.getPrototypeOf(target.prototype).constructor;
var parentAnnotations = Reflect.getMetadata('annotations', parentTarget);
var parentAnnotation = parentAnnotations[0];
Object.keys(parentAnnotation).forEach(key => {
if (isPresent(parentAnnotation[key])) {
// verify is annotation typeof function
if(typeof annotation[key] === 'function'){
annotation[key] = annotation[key].call(this, parentAnnotation[key]);
}else if(
// force override in annotation base
!isPresent(annotation[key])
){
annotation[key] = parentAnnotation[key];
}
}
});
var metadata = new Component(annotation);
Reflect.defineMetadata('annotations', [ metadata ], target);
}
}
然后将其导入新组件sub-component.component.ts 文件并使用@CustomComponent 而不是@Component,如下所示:
import { CustomComponent } from './CustomDecorator';
import { AbstractComponent } from 'path/to/file';
...
@CustomComponent({
selector: 'subcomponent'
})
export class SubComponent extends AbstractComponent {
constructor() {
super();
}
// Add new logic here!
}
【讨论】:
export class ChildComponent extends ParentComponent。这样 Angular 就知道要寻找哪个父级了!
@angular/platform-browser Plunker 能够找到 isPresent ,我在 Reflect.getMetadata('annotations', parentTarget) 遇到问题它返回空对象。
import 'zone.js'; 和import 'reflect-metadata'; 的地方。你知道是否有办法在 Plunker 中访问这些?
以防万一您正在寻找 isPresent 功能:
function isPresent(obj: any): boolean {
return obj !== undefined && obj !== null;
}
【讨论】: