使用基类装饰器扩展组件装饰器

dst*_*str 19 typescript angular

我有几个组件装饰器声明,我在每个组件上重复,例如:

@Component({
    moduleId: module.id,
    directives: [BootstrapInputDirective]
})
Run Code Online (Sandbox Code Playgroud)

如何将这些声明应用于我的所有组件?我试图用这个装饰器创建一个基类,并用它扩展其他类,但基类装饰似乎不适用于派生类.

Thi*_*ier 23

@Component是装饰师.这意味着它通过添加一些利用reflect-metadata库的元数据来处理它所应用的类.Angular2不会查找父类的元数据.因此,不能在父类上使用装饰器.

关于BootstrapInputDirective指令,您可以将其定义为平台指令.这样,您不需要每次都将它包含directives在组件的属性中.

这是一个示例:

(...)
import {PLATFORM_DIRECTIVES} from 'angular2/core';

bootstrap(AppComponent, [
  provide(PLATFORM_DIRECTIVES, {useValue: [BootstrapInputDirective], multi:true})
]);
Run Code Online (Sandbox Code Playgroud)

编辑

是的,您可以创建自己的装饰器来实现这一点.这是一个示例:

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

CustomComponent装饰将采用这种方式:

@Component({
  template: `
    <div>Test</div>
  `
})
export class AbstractComponent {
}

@CustomComponent({
  selector: 'sub',
  parent: AbstractComponent
})
export class SubComponent extends AbstractComponent {
}
Run Code Online (Sandbox Code Playgroud)

请注意,我们需要提供父类作为装饰器的输入,因为我们可以在装饰器中找到这个父类.只有该类的原型,但元数据应用于类,而不是通过reflect-metadata应用于关联的原型.

EDIT2

感谢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);
  }
}
Run Code Online (Sandbox Code Playgroud)

不需要parent属性来引用自定义装饰器中的父类.

请参阅此plunkr:https://plnkr.co/edit/ks1iK41sIBFlYDb4aTHG p = preview .

看到这个问题:

  • ComponentMetadata不再是核心......有没有办法在2.1.0中使用它? (3认同)

Gui*_*erg 8

在最新版本的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];
      });
  };
}
Run Code Online (Sandbox Code Playgroud)

希望能帮助到你!

编辑:前面的代码块,即使它工作,它也会覆盖扩展类的原始元数据.在下面找到它的增强版本,允许您具有多个继承和覆盖而无需修改基类.

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];
    });
};
Run Code Online (Sandbox Code Playgroud)

}


rhy*_*eav 6

如果有人正在寻找更新的解决方案,那么Thierry Templier的答案非常完美.除了ComponentMetadata已被弃用.使用Component相反为我工作.

完整的Custom Decorator 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);
  }
}
Run Code Online (Sandbox Code Playgroud)

然后将其导入到新的组件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!
}
Run Code Online (Sandbox Code Playgroud)

  • 如果你只是使用`@ angular/platform-b​​rowser`,Plunker能够找到`isPresent`,我在`Reflect.getMetadata('annotations',parentTarget)面临问题`它返回null对象. (2认同)

dip*_*ore 5

以防您正在寻找isPresent功能:

function isPresent(obj: any): boolean { return obj !== undefined && obj !== null; }