typescript - versiones - Decorador de componentes extensible con decorador de clase base
install angular (5)
Tengo varias declaraciones de decorador de componentes que repito en cada componente, por ejemplo:
@Component({
moduleId: module.id,
directives: [BootstrapInputDirective]
})
¿Cómo puedo aplicar estas declaraciones a todos mis componentes? Intenté crear una clase base con este decorador y extender otras clases con él, pero las decoraciones de la clase base no parecen aplicarse a las clases derivadas.
Después de los últimos lanzamientos de Angular, la clase ComponentMetadata no está disponible como lo señalan algunos miembros aquí.
Así es como he implementado el CustomComponent para que funcione:
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];
});
};
}
¡Espero eso ayude!
EDITAR : el fragmento de código anterior, incluso si funciona, anula los metadatos originales de la clase extendida. Encuentre a continuación una versión mejorada del mismo, que le permite tener múltiples herencias y anulaciones sin modificar la clase base.
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];
});
};
}
En caso de que esté buscando la función isPresent:
function isPresent(obj: any): boolean { return obj !== undefined && obj !== null; }
Puede proporcionar servicios a nivel mundial en su función de arranque como:
bootstrap(AppComponent, [HTTP_PROVIDERS, provide(SharedService, {useValue: sharedService})]);
donde sharedService es su servicio importado.
Si alguien está buscando una solución actualizada, la respuesta de Thierry Templier es bastante perfecta.
Excepto que
ComponentMetadata
ha quedado en desuso.
Usar
Component
lugar funcionó para mí.
El archivo completo Custom Decorator
CustomDecorator.ts
tiene este aspecto:
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);
}
}
Luego impórtelo a su nuevo componente
sub-component.component.ts
y use
@CustomComponent
lugar de
@Component
esta manera:
import { CustomComponent } from ''./CustomDecorator'';
import { AbstractComponent } from ''path/to/file'';
...
@CustomComponent({
selector: ''subcomponent''
})
export class SubComponent extends AbstractComponent {
constructor() {
super();
}
// Add new logic here!
}
@Component
es un decorador.
Esto significa que maneja la clase a la que se aplica agregando algunos datos de metadatos que aprovechan la biblioteca reflect-metadata.
Angular2 no busca metadatos en las clases primarias.
Por esta razón, no es posible usar decoradores en las clases primarias.
Con respecto a la directiva
BootstrapInputDirective
, puede definirla como una plataforma.
De esta manera, no necesitaría incluirlo cada vez en el atributo de
directives
de sus componentes.
Aquí hay una muestra:
(...)
import {PLATFORM_DIRECTIVES} from ''angular2/core'';
bootstrap(AppComponent, [
provide(PLATFORM_DIRECTIVES, {useValue: [BootstrapInputDirective], multi:true})
]);
Editar
Sí, podría crear su propio decorador para implementar esto. Aquí hay una muestra:
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);
}
}
El decorador
CustomComponent
se usará de esta manera:
@Component({
template: `
<div>Test</div>
`
})
export class AbstractComponent {
}
@CustomComponent({
selector: ''sub'',
parent: AbstractComponent
})
export class SubComponent extends AbstractComponent {
}
Tenga en cuenta que debemos proporcionar la clase principal como entrada del decorador ya que podemos encontrar esta clase principal dentro del decorador. Solo el prototipo de esta clase pero los metadatos se aplican en la clase y no en el prototipo asociado por reflect-metadata.
Edit2
Gracias a la respuesta de Nitzam, aquí hay una mejora:
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);
}
}
No es necesario que un atributo
parent
haga referencia a la clase padre en el decorador personalizado.
Vea este plunkr: https://plnkr.co/edit/ks1iK41sIBFlYDb4aTHG?p=preview .
Ver esta pregunta: