pasar parametros entre ejemplo datos comunicacion componentes angular observer-pattern observable eventemitter event-delegation

parametros - pasar datos entre componentes angular 5



DelegaciĆ³n: EventEmitter u Observable en Angular (7)

Estoy tratando de implementar algo como un patrón de delegación en Angular. Cuando el usuario hace clic en un nav-item , me gustaría llamar a una función que luego emite un evento que a su vez debe ser manejado por algún otro componente que escuche el evento.

Aquí está el escenario: tengo un componente de Navigation :

import {Component, Output, EventEmitter} from ''angular2/core''; @Component({ // other properties left out for brevity events : [''navchange''], template:` <div class="nav-item" (click)="selectedNavItem(1)"></div> ` }) export class Navigation { @Output() navchange: EventEmitter<number> = new EventEmitter(); selectedNavItem(item: number) { console.log(''selected nav item '' + item); this.navchange.emit(item) } }

Aquí está el componente de observación:

export class ObservingComponent { // How do I observe the event ? // <----------Observe/Register Event ?--------> public selectedNavItem(item: number) { console.log(''item index changed!''); } }

La pregunta clave es, ¿cómo hago para que el componente de observación observe el evento en cuestión?


Debe usar el componente de navegación en la plantilla de ObservingComponent (no olvide agregar un selector al componente de navegación ... componente de navegación para ex)

<navigation-component (navchange)=''onNavGhange($event)''></navigation-component>

E implementar onNavGhange () en ObservingComponent

onNavGhange(event) { console.log(event); }

Lo último ... no necesitas el atributo de eventos en @Componennt

events : [''navchange''],


Encontré otra solución para este caso sin usar Reactivex ni servicios. De hecho, me encanta la API rxjx, sin embargo, creo que funciona mejor cuando se resuelve una función asincrónica y / o compleja. Utilizándolo de esa manera, es bastante excedido para mí.

Lo que creo que estás buscando es una transmisión. Solo eso. Y descubrí esta solución:

<app> <app-nav (selectedTab)="onSelectedTab($event)"></app-nav> // This component bellow wants to know when a tab is selected // broadcast here is a property of app component <app-interested [broadcast]="broadcast"></app-interested> </app> @Component class App { broadcast: EventEmitter<tab>; constructor() { this.broadcast = new EventEmitter<tab>(); } onSelectedTab(tab) { this.broadcast.emit(tab) } } @Component class AppInterestedComponent implements OnInit { broadcast: EventEmitter<Tab>(); doSomethingWhenTab(tab){ ... } ngOnInit() { this.broadcast.subscribe((tab) => this.doSomethingWhenTab(tab)) } }

Este es un ejemplo de trabajo completo: https://plnkr.co/edit/xGVuFBOpk2GP0pRBImsE


Si se quiere seguir un estilo de programación más reactivo, entonces definitivamente aparece el concepto de "Todo es un flujo" y, por lo tanto, se utilizan los Observables para manejar estos flujos con la mayor frecuencia posible.


puede usar BehaviourSubject como se describió anteriormente o hay una forma más:

puede manejar EventEmitter de esta manera: primero agregue un selector

import {Component, Output, EventEmitter} from ''angular2/core''; @Component({ // other properties left out for brevity selector: ''app-nav-component'', //declaring selector template:` <div class="nav-item" (click)="selectedNavItem(1)"></div> ` }) export class Navigation { @Output() navchange: EventEmitter<number> = new EventEmitter(); selectedNavItem(item: number) { console.log(''selected nav item '' + item); this.navchange.emit(item) } }

Ahora puede manejar este evento como supongamos que observer.component.html es la vista del componente Observador

<app-nav-component (navchange)="recieveIdFromNav($event)"></app-nav-component>

luego en el ObservingComponent.ts

export class ObservingComponent { //method to recieve the value from nav component public recieveIdFromNav(id: number) { console.log(''here is the id sent from nav component '', id); } }


Actualización 2016-06-27: en lugar de usar Observables, use cualquiera

  • un BehaviorSubject, según lo recomendado por @Abdulrahman en un comentario, o
  • un ReplaySubject, según lo recomendado por @Jason Goemaat en un comentario

Un Subject es tanto un Observable (para que podamos subscribe() ) como un Observador (para que podamos llamar a next() en él para emitir un nuevo valor). Explotamos esta característica. Un sujeto permite que los valores sean multidifusión para muchos observadores. No explotamos esta característica (solo tenemos un observador).

BehaviorSubject es una variante de Subject. Tiene la noción de "el valor actual". Explotamos esto: cada vez que creamos un componente Observador, obtiene el valor del elemento de navegación actual del BehaviorSubject automáticamente.

El código a continuación y el plunker usan BehaviorSubject.

ReplaySubject es otra variante de Subject. Si desea esperar hasta que se produzca un valor, use ReplaySubject(1) . Mientras que BehaviorSubject requiere un valor inicial (que se proporcionará de inmediato), ReplaySubject no. ReplaySubject siempre proporcionará el valor más reciente, pero dado que no tiene un valor inicial requerido, el servicio puede realizar algunas operaciones asíncronas antes de devolver su primer valor. Todavía se disparará inmediatamente en llamadas posteriores con el valor más reciente. Si solo desea un valor, use first() en la suscripción. No tiene que darse de baja si usa first() .

import {Injectable} from ''@angular/core'' import {BehaviorSubject} from ''rxjs/BehaviorSubject''; @Injectable() export class NavService { // Observable navItem source private _navItemSource = new BehaviorSubject<number>(0); // Observable navItem stream navItem$ = this._navItemSource.asObservable(); // service command changeNav(number) { this._navItemSource.next(number); } }

import {Component} from ''@angular/core''; import {NavService} from ''./nav.service''; import {Subscription} from ''rxjs/Subscription''; @Component({ selector: ''obs-comp'', template: `obs component, item: {{item}}` }) export class ObservingComponent { item: number; subscription:Subscription; constructor(private _navService:NavService) {} ngOnInit() { this.subscription = this._navService.navItem$ .subscribe(item => this.item = item) } ngOnDestroy() { // prevent memory leak when component is destroyed this.subscription.unsubscribe(); } }

@Component({ selector: ''my-nav'', template:` <div class="nav-item" (click)="selectedNavItem(1)">nav 1 (click me)</div> <div class="nav-item" (click)="selectedNavItem(2)">nav 2 (click me)</div>` }) export class Navigation { item = 1; constructor(private _navService:NavService) {} selectedNavItem(item: number) { console.log(''selected nav item '' + item); this._navService.changeNav(item); } }

plunker

Respuesta original que usa un Observable: (requiere más código y lógica que usar un BehaviorSubject, por lo que no lo recomiendo, pero puede ser instructivo)

Entonces, aquí hay una implementación que usa un Observable en lugar de un EventEmitter . A diferencia de mi implementación EventEmitter, esta implementación también almacena el elemento de navItem actualmente seleccionado en el servicio, de modo que cuando se crea un componente de observación, puede recuperar el valor actual a través de la llamada API navItem() , y luego ser notificado de los cambios a través de navChange$ Observable .

import {Observable} from ''rxjs/Observable''; import ''rxjs/add/operator/share''; import {Observer} from ''rxjs/Observer''; export class NavService { private _navItem = 0; navChange$: Observable<number>; private _observer: Observer; constructor() { this.navChange$ = new Observable(observer => this._observer = observer).share(); // share() allows multiple subscribers } changeNav(number) { this._navItem = number; this._observer.next(number); } navItem() { return this._navItem; } } @Component({ selector: ''obs-comp'', template: `obs component, item: {{item}}` }) export class ObservingComponent { item: number; subscription: any; constructor(private _navService:NavService) {} ngOnInit() { this.item = this._navService.navItem(); this.subscription = this._navService.navChange$.subscribe( item => this.selectedNavItem(item)); } selectedNavItem(item: number) { this.item = item; } ngOnDestroy() { this.subscription.unsubscribe(); } } @Component({ selector: ''my-nav'', template:` <div class="nav-item" (click)="selectedNavItem(1)">nav 1 (click me)</div> <div class="nav-item" (click)="selectedNavItem(2)">nav 2 (click me)</div> `, }) export class Navigation { item:number; constructor(private _navService:NavService) {} selectedNavItem(item: number) { console.log(''selected nav item '' + item); this._navService.changeNav(item); } }

Plunker

Consulte también el ejemplo del Libro de cocina de interacción de componentes , que utiliza un Subject además de los observables. Aunque el ejemplo es "comunicación entre padres e hijos", la misma técnica es aplicable para componentes no relacionados.


Noticias de última hora: agregué otra respuesta que usa un Observable en lugar de un EventEmitter. Recomiendo esa respuesta sobre esta. Y en realidad, usar un EventEmitter en un servicio es una mala práctica .

Respuesta original: (no hagas esto)

Ponga el EventEmitter en un servicio, que permite que ObservingComponent se suscriba directamente (y cancele la suscripción) al evento :

import {EventEmitter} from ''angular2/core''; export class NavService { navchange: EventEmitter<number> = new EventEmitter(); constructor() {} emit(number) { this.navchange.emit(number); } subscribe(component, callback) { // set ''this'' to component when callback is called return this.navchange.subscribe(data => call.callback(component, data)); } } @Component({ selector: ''obs-comp'', template: ''obs component, index: {{index}}'' }) export class ObservingComponent { item: number; subscription: any; constructor(private navService:NavService) { this.subscription = this.navService.subscribe(this, this.selectedNavItem); } selectedNavItem(item: number) { console.log(''item index changed!'', item); this.item = item; } ngOnDestroy() { this.subscription.unsubscribe(); } } @Component({ selector: ''my-nav'', template:` <div class="nav-item" (click)="selectedNavItem(1)">item 1 (click me)</div> `, }) export class Navigation { constructor(private navService:NavService) {} selectedNavItem(item: number) { console.log(''selected nav item '' + item); this.navService.emit(item); } }

Si prueba el Plunker , hay algunas cosas que no me gustan de este enfoque:

  • ObservingComponent necesita darse de baja cuando se destruye
  • tenemos que pasar el componente a subscribe() para que se establezca correctamente cuando se llama a la devolución de llamada

Actualización: una alternativa que resuelve la segunda viñeta es hacer que ObservingComponent se suscriba directamente a la propiedad navchange EventEmitter:

constructor(private navService:NavService) { this.subscription = this.navService.navchange.subscribe(data => this.selectedNavItem(data)); }

Si nos suscribimos directamente, entonces no necesitaríamos el método subscribe() en el NavService.

Para hacer que NavService esté un poco más encapsulado, puede agregar un método getNavChangeEmitter() y usar eso:

getNavChangeEmitter() { return this.navchange; } // in NavService constructor(private navService:NavService) { // in ObservingComponent this.subscription = this.navService.getNavChangeEmitter().subscribe(data => this.selectedNavItem(data)); }


Puedes usar:

  1. Sujeto de comportamiento:

BehaviorSubject es un tipo de sujeto, un sujeto es un tipo especial de observable que puede actuar como observable y observador puede suscribirse a mensajes como cualquier otro observable y al suscribirse, devuelve el último valor del sujeto emitido por la fuente observable:

Ventaja: No se requiere una relación como la relación padre-hijo para pasar datos entre componentes.

SERVICIO NAV

import {Injectable} from ''@angular/core'' import {BehaviorSubject} from ''rxjs/BehaviorSubject''; @Injectable() export class NavService { private navSubject$ = new BehaviorSubject<number>(0); constructor() { } // Event New Item Clicked navItemClicked(navItem: number) { this.navSubject$.next(number); } // Allowing Observer component to subscribe emitted data only getNavItemClicked$() { return this.navSubject$.asObservable(); } }

COMPONENTE DE NAVEGACIÓN

@Component({ selector: ''navbar-list'', template:` <ul> <li><a (click)="navItemClicked(1)">Item-1 Clicked</a></li> <li><a (click)="navItemClicked(2)">Item-2 Clicked</a></li> <li><a (click)="navItemClicked(3)">Item-3 Clicked</a></li> <li><a (click)="navItemClicked(4)">Item-4 Clicked</a></li> </ul> }) export class Navigation { constructor(private navService:NavService) {} navItemClicked(item: number) { this.navService.navItemClicked(item); } }

OBSERVANDO EL COMPONENTE

@Component({ selector: ''obs-comp'', template: `obs component, item: {{item}}` }) export class ObservingComponent { item: number; itemClickedSubcription:any constructor(private navService:NavService) {} ngOnInit() { this.itemClickedSubcription = this.navService .getNavItemClicked$ .subscribe( item => this.selectedNavItem(item) ); } selectedNavItem(item: number) { this.item = item; } ngOnDestroy() { this.itemClickedSubcription.unsubscribe(); } }

El segundo enfoque es la Event Delegation in upward direction child -> parent

  1. Usando los decoradores @Input y @Output los datos de paso primario al componente secundario y el componente primario de notificación secundaria

Por ejemplo, respondida por @Ashish Sharma.