page navigationend change angular2 angular typescript inheritance angular2-directives angular-directive

navigationend - router events subscribe angular 4



¿Cómo extender/heredar componentes? (10)

Me gustaría crear extensiones para algunos componentes ya implementados en Angular 2, sin tener que reescribirlos casi por completo, ya que el componente base podría sufrir cambios y desear que estos cambios también se reflejen en sus componentes derivados.

Creé este ejemplo simple para tratar de explicar mejor mis preguntas:

Con el siguiente componente base app/base-panel.component.ts :

import {Component, Input} from ''angular2/core''; @Component({ selector: ''base-panel'', template: ''<div class="panel" [style.background-color]="color" (click)="onClick($event)">{{content}}</div>'', styles: [` .panel{ padding: 50px; } `] }) export class BasePanelComponent { @Input() content: string; color: string = "red"; onClick(event){ console.log("Click color: " + this.color); } }

¿Desea crear otro componente derivado solo alterar, por ejemplo, un comportamiento de componente básico en el caso del color de ejemplo, app/my-panel.component.ts :

import {Component} from ''angular2/core''; import {BasePanelComponent} from ''./base-panel.component'' @Component({ selector: ''my-panel'', template: ''<div class="panel" [style.background-color]="color" (click)="onClick($event)">{{content}}</div>'', styles: [` .panel{ padding: 50px; } `] }) export class MyPanelComponent extends BasePanelComponent{ constructor() { super(); this.color = "blue"; } }

Ejemplo de trabajo completo en Plunker

Nota: Obviamente, este ejemplo es simple y podría resolverse; de ​​lo contrario, no es necesario utilizar la herencia, pero solo tiene la intención de ilustrar el problema real.

Como puede ver en la implementación del componente derivado app/my-panel.component.ts , gran parte de la implementación se repitió, y la única parte realmente heredada fue la class BasePanelComponent , pero el @Component tuvo que repetirse básicamente por completo, no solo las porciones cambiadas, como el selector: ''my-panel'' .

¿Hay alguna manera de hacer una herencia literalmente completa de un componente Angular2, heredando la definición de class de las marcas / anotaciones, como por ejemplo @Component ?

Edición 1 - Solicitud de funciones

Solicitud de función angular2 agregada al proyecto en GitHub: Extender / Heredar anotaciones de componentes angular2 # 7968

Edición 2 - Solicitud cerrada

La solicitud se cerró, por este motivo , que brevemente no sabría cómo fusionar el decorador se realizará. Dejándonos sin opciones. Así que mi opinión se cita en el número .


Ahora que TypeScript 2.2 admite Mixins a través de expresiones de clase , tenemos una forma mucho mejor de expresar Mixins en componentes. Tenga en cuenta que también puede usar la herencia de componentes desde angular 2.3 ( discussion ) o un decorador personalizado como se discute en otras respuestas aquí. Sin embargo, creo que los Mixins tienen algunas propiedades que los hacen preferibles para reutilizar el comportamiento entre los componentes:

  • Los Mixins componen de manera más flexible, es decir, puede mezclar y combinar Mixins en componentes existentes o combinar Mixins para formar nuevos Componentes
  • La composición de Mixin sigue siendo fácil de entender gracias a su linealización obvia a una jerarquía de herencia de clase
  • Puede evitar más fácilmente problemas con decoradores y anotaciones que afectan a la herencia de componentes ( discussion )

Le sugiero que lea el anuncio de TypeScript 2.2 anterior para comprender cómo funcionan los Mixins. Las discusiones vinculadas en los problemas angulares de GitHub proporcionan detalles adicionales.

Necesitarás estos tipos:

export type Constructor<T> = new (...args: any[]) => T; export class MixinRoot { }

Y luego puede declarar un Mixin como este Mixin Destroyable que ayuda a los componentes a realizar un seguimiento de las suscripciones que deben eliminarse en ngOnDestroy :

export function Destroyable<T extends Constructor<{}>>(Base: T) { return class Mixin extends Base implements OnDestroy { private readonly subscriptions: Subscription[] = []; protected registerSubscription(sub: Subscription) { this.subscriptions.push(sub); } public ngOnDestroy() { this.subscriptions.forEach(x => x.unsubscribe()); this.subscriptions.length = 0; // release memory } }; }

Para mezclar Destroyable en un Component , declaras tu componente así:

export class DashboardComponent extends Destroyable(MixinRoot) implements OnInit, OnDestroy { ... }

Tenga en cuenta que MixinRoot solo es necesario cuando desea extend una composición de Mixin. Puede extender fácilmente múltiples mixins, por ejemplo, A extends B(C(D)) . Esta es la linealización obvia de los mixins de los que estaba hablando anteriormente, por ejemplo, está componiendo efectivamente una jerarquía de herencia A -> B -> C -> D

En otros casos, por ejemplo, cuando desea componer Mixins en una clase existente, puede aplicar Mixin de la siguiente manera:

const MyClassWithMixin = MyMixin(MyClass);

Sin embargo, descubrí que la primera forma funciona mejor para Components y Directives , ya que estos también deben ser decorados con @Component o @Directive todos modos.



Hasta donde sé, la herencia de componentes aún no se ha implementado en Angular 2 y no estoy seguro de si tienen planes, sin embargo, dado que Angular 2 está usando el mecanografiado (si ha decidido seguir esa ruta), puede usar la herencia de clase haciendo class MyClass extends OtherClass { ... } . Para la herencia de componentes, sugeriría involucrarse con el proyecto Angular 2 yendo a https://github.com/angular/angular/issues y enviando una solicitud de función.


Los componentes se pueden extender igual que una herencia de clase mecanografiada, solo que tiene que anular el selector con un nuevo nombre. Todas las propiedades de entrada () y salida () del componente principal funcionan normalmente

Actualizar

@Component es un decorador,

Los decoradores se aplican durante la declaración de clase no en los objetos.

Básicamente, los decoradores agregan algunos metadatos al objeto de clase y no se puede acceder a ellos a través de la herencia.

Si desea lograr la herencia del decorador, le sugiero que escriba un decorador personalizado. Algo como el siguiente ejemplo.

export function CustomComponent(annotation: any) { return function (target: Function) { var parentTarget = Object.getPrototypeOf(target.prototype).constructor; var parentAnnotations = Reflect.getMetadata(''annotations'', parentTarget); var parentParamTypes = Reflect.getMetadata(''design:paramtypes'', parentTarget); var parentPropMetadata = Reflect.getMetadata(''propMetadata'', parentTarget); var parentParameters = Reflect.getMetadata(''parameters'', parentTarget); var parentAnnotation = parentAnnotations[0]; Object.keys(parentAnnotation).forEach(key => { if (isPresent(parentAnnotation[key])) { if (!isPresent(annotation[key])) { annotation[key] = parentAnnotation[key]; } } }); // Same for the other metadata var metadata = new ComponentMetadata(annotation); Reflect.defineMetadata(''annotations'', [ metadata ], target); }; };

Consulte: https://medium.com/@ttemplier/angular2-decorators-and-class-inheritance-905921dbd1b7


Permítanos entender algunas limitaciones y características clave en el sistema de herencia de componentes de Angular.

El componente solo hereda la lógica de clase. Todos los metadatos del decorador @Component no se heredan. Las propiedades @Input y las propiedades @Output se heredan. El ciclo de vida del componente no se hereda. .

El Componente solo hereda la lógica de la clase Cuando hereda un Componente, toda la lógica interna es igualmente heredada. Vale la pena señalar que solo los miembros públicos se heredan ya que los miembros privados solo son accesibles en la clase que los implementa. Todos los metadatos en el decorador @Component no se heredan El hecho de que no se hereden metadatos puede parecer contradictorio al principio, pero, si piensas en esto, tiene mucho sentido. Si hereda de un Componente, digamos (componenteA), no querrá que el selector de ComponenteA, del que está heredando, reemplace el selector de ComponenteB, que es la clase que está heredando. Lo mismo puede decirse de template / templateUrl y de style / styleUrls.

Las propiedades del componente @Input y @Output se heredan

Esta es otra característica que realmente me encanta de la herencia de componentes en Angular. En una oración simple, siempre que tenga una propiedad personalizada @Input y @Ouput, estas propiedades se heredan.

El ciclo de vida de los componentes no se hereda Esta parte es la que no es tan obvia, especialmente para las personas que no han trabajado extensamente con los principios de la POO. Por ejemplo, supongamos que tiene ComponentA que implementa uno de los muchos ganchos de ciclo de vida de Angular como OnInit. Si crea ComponentB y hereda ComponentA, el ciclo de vida OnInit de ComponentA no se activará hasta que lo llame explícitamente, incluso si tiene este ciclo de vida OnInit para ComponentB.

Invocación de métodos de componentes Super / Base Para poder activar el método ngOnInit () de ComponentA, debemos usar la palabra clave super y luego llamar al método que necesitamos, que en este caso es ngOnInit. La palabra clave super se refiere a la instancia del componente que se hereda del cual en este caso será ComponentA.


Sé que esto no responde a su pregunta, pero realmente creo que se debe evitar heredar / extender componentes . Aquí está mi razonamiento:

Si la clase abstracta extendida por dos o más componentes contiene lógica compartida: use un servicio o incluso cree una nueva clase de mecanografiado que pueda compartirse entre los dos componentes.

Si la clase abstracta ... contiene variables compartidas o funciones onClicketc, entonces habrá duplicación entre el html de las dos vistas de componentes extendidos. Esta es una mala práctica y que html compartido debe dividirse en Componente (s). Estos componentes (partes) se pueden compartir entre los dos componentes.

¿Me faltan otras razones para tener una clase abstracta para componentes?

Un ejemplo que vi recientemente fue componentes que extienden AutoUnsubscribe:

import { Subscription } from ''rxjs''; import { OnDestroy } from ''@angular/core''; export abstract class AutoUnsubscribeComponent implements OnDestroy { protected infiniteSubscriptions: Array<Subscription>; constructor() { this.infiniteSubscriptions = []; } ngOnDestroy() { this.infiniteSubscriptions.forEach((subscription) => { subscription.unsubscribe(); }); } }

esto fue bas porque en una base de código grande, infiniteSubscriptions.push () solo se usó 10 veces. Además, la importación y extensión de AutoUnsubscribe en realidad requiere más código que solo agregar mySubscription.unsubscribe () en el método ngOnDestroy () del componente en sí, que de todos modos requería lógica adicional.


Si alguien está buscando una solución actualizada, la respuesta de Fernando 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! }


Solución alternativa:

Esta respuesta de Thierry Templier es una forma alternativa de solucionar el problema.

Después de algunas preguntas con Thierry Templier, llegué al siguiente ejemplo de trabajo que cumple con mis expectativas como una alternativa a la limitación de herencia mencionada en esta pregunta:

1 - Crear decorador personalizado:

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

2 - Componente base con decorador @Component:

@Component({ // create seletor base for test override property selector: ''master'', template: ` <div>Test</div> ` }) export class AbstractComponent { }

3 - Subcomponente con decorador @CustomComponent:

@CustomComponent({ // override property annotation //selector: ''sub'', selector: (parentSelector) => { return parentSelector + ''sub''} }) export class SubComponent extends AbstractComponent { constructor() { } }

Plunkr con ejemplo completo.


actualizar

La herencia de componentes es compatible desde 2.3.0-rc.0

original

Hasta ahora, lo más conveniente para mí es mantener plantillas y estilos en archivos *html y *.css separados y especificarlos a través de templateUrl y styleUrls , por lo que es fácil de reutilizar.

@Component { selector: ''my-panel'', templateUrl: ''app/components/panel.html'', styleUrls: [''app/components/panel.css''] } export class MyPanelComponent extends BasePanelComponent


just use inheritance,Extend parent class in child class and declare constructor with parent class parameter and this parameter use in super(). 1.parent class @Component({ selector: ''teams-players-box'', templateUrl: ''/maxweb/app/app/teams-players-box.component.html'' }) export class TeamsPlayersBoxComponent { public _userProfile:UserProfile; public _user_img:any; public _box_class:string="about-team teams-blockbox"; public fullname:string; public _index:any; public _isView:string; indexnumber:number; constructor( public _userProfilesSvc: UserProfiles, public _router:Router, ){} 2.child class @Component({ selector: ''[teams-players-eligibility]'', templateUrl: ''/maxweb/app/app/teams-players-eligibility.component.html'' }) export class TeamsPlayersEligibilityComponent extends TeamsPlayersBoxComponent{ constructor (public _userProfilesSvc: UserProfiles, public _router:Router) { super(_userProfilesSvc,_router); } }