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

时间:2016-04-25 10:01:48

标签: typescript angular

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

@Component({
    moduleId: module.id,
    directives: [BootstrapInputDirective]
})

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

5 个答案:

答案 0 :(得分:22)

@Component是装饰者。这意味着它通过添加一些利用reflect-metadata库的元数据来处理它所应用的类。 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 {
}

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

<强> 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);
  }
}

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

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

看到这个问题:

答案 1 :(得分: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];
      });
  };
}

希望它有所帮助!

修改: 前面的代码块,即使它工作,它也会覆盖扩展类的原始元数据。在下面找到它的增强版本,允许您在不修改基类的情况下拥有多个继承和覆盖。

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];
    });
};

}

答案 2 :(得分: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);
  }
}

然后将其导入新的组件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!
}

答案 3 :(得分:5)

以防您正在寻找isPresent功能:

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

答案 4 :(得分:1)

您可以在引导功能中全局提供服务,例如:

bootstrap(AppComponent, [HTTP_PROVIDERS, provide(SharedService, {useValue: sharedService})]);

其中sharedService是您导入的服务。