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