Расширитель компонентов с декоратором базового класса
У меня есть несколько описателей декораторов компонентов, которые я повторяю для каждого компонента, например:
@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 - это импортированная служба.