Расширитель компонентов с декоратором базового класса

У меня есть несколько описателей декораторов компонентов, которые я повторяю для каждого компонента, например:

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

Edit

Да, вы можете создать свой собственный декоратор, чтобы реализовать это. Вот пример:

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.

Смотрите этот вопрос:

Ответ 2

После последних выпусков 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];
    });
};

}

Ответ 3

Если кто-то ищет обновленное решение, ответ Тьерри Темплиера в значительной степени идеален. За исключением того, что 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!
}

Ответ 4

На всякий случай вы ищете функцию isPresent:

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

Ответ 5

Вы можете предоставлять услуги по всему миру в своей начальной загрузке, например:

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

где sharedService - это импортированная служба.