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

Posted

技术标签:

【中文标题】使用基类装饰器扩展组件装饰器【英文标题】:Extending component decorator with base class decorator 【发布时间】:2016-08-18 15:47:35 【问题描述】:

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

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

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

【问题讨论】:

【参考方案1】:

@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。

看到这个问题:

How to get the parent class at runtime

【讨论】:

是否可以实现我自己的 Component 装饰器,它扩展了 @Component ,它具有我自己的默认值,例如 moduleId 属性? @ThierryTemplier,在我的场景中,我需要覆盖基本组件中的一些属性,然后调整他的example。您可以分析为测试我的场景而实施的更改吗?更改的行带有带有标记的评论:CHECK:。基本上尝试的是permiter覆盖注释属性并可以访问组件基础的属性以合并到子组件语句中(失败=/) @ThierryTemplier,我相信它类似于this,您建议与函数装饰器相关,它检查属性是否为function,以及function 是否运行基值作为参数。正确的?我相信还有一些调整,例如:多继承级别(N 级别),在同一上下文中注入多个级别的指令,合并属性中的分辨率等。可以在 this issue of Angular2 project? 中提出类似的建议?跨度> @Fernando 我看了你的 plunkr,这正是我的想法;-) 有趣的想法!我正在写一篇关于此的文章,因此我可以实现它并将其添加到文章中。我会让你知道... ComponentMetadata 不再是核心......有没有办法在 2.1.0 中使用它?【参考方案2】:

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

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

其中 sharedService 是您导入的服务。

【讨论】:

【参考方案3】:

在最新版本的 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];
    );
;

【讨论】:

添加了真正的继承装饰器,而不是仅仅覆盖基础装饰器【参考方案4】:

如果有人正在寻找更新的解决方案,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!

【讨论】:

你有这个 Plunker 吗?我正在尝试这个但不工作,检查这个Plunker。 我曾短暂尝试过制作 Plunker,但看起来 Plunker 无法从 @angular/platform-b​​rowser/src/facade/lang 找到 isPresent(它会引发 500 错误)。此自定义组件用于从其父级继承信息。如果你想使用另一个组件的模板,但使用不同的 TypeScript 函数,这很有用。此外,请确保您正在扩展您的子组件:export class ChildComponent extends ParentComponent。这样 Angular 就知道要寻找哪个父级了! 如果你只使用 @angular/platform-browser Plunker 能够找到 isPresent ,我在 Reflect.getMetadata('annotations', parentTarget) 遇到问题它返回空对象。 哦,很高兴知道,谢谢!这个问题可能是需要import 'zone.js';import 'reflect-metadata'; 的地方。你知道是否有办法在 Plunker 中访问这些?【参考方案5】:

以防万一您正在寻找 isPresent 功能:

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

【讨论】:

以上是关于使用基类装饰器扩展组件装饰器的主要内容,如果未能解决你的问题,请参考以下文章

PHP设计模式-装饰器模式

作为基类一部分的 Python 装饰器不能用于装饰继承类中的成员函数

设计模式----装饰器模式

在基类和派生类中使用基类的装饰器

通过 Angular 2 中的 Input 装饰器使用多个属性

是否可以在 Typescript 中装饰装饰器?