Recuerda que en la informática un problema casi siempre tiene varias soluciones, pero aquí compartimos lo más óptimo y mejor.
Solución:
Actualización 2016-06-27: en lugar de usar Observables, use
- 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 sujeto es tanto observable (por lo que podemos subscribe()
a él) y un observador (por lo que podemos llamar next()
sobre él para emitir un nuevo valor). Aprovechamos esta característica. Un sujeto permite la multidifusión de valores a muchos observadores. No aprovechamos esta función (solo tenemos un observador).
BehaviorSubject es una variante de Subject. Tiene la noción de “valor actual”. Aprovechamos esto: cada vez que creamos un ObservingComponent, obtiene el valor del elemento de navegación actual del BehaviorSubject automáticamente.
El código siguiente y el plunker usan BehaviorSubject.
ReplaySubject es otra variante de Subject. Si desea esperar hasta que se produzca un valor, utilice ReplaySubject(1)
. Mientras que un BehaviorSubject requiere un valor inicial (que se proporcionará inmediatamente), ReplaySubject no. ReplaySubject siempre proporcionará el valor más reciente, pero como no tiene un valor inicial requerido, el servicio puede realizar alguna operación asincrónica antes de devolver su primer valor. Aún se activará inmediatamente en llamadas posteriores con el valor más reciente. Si solo quiere un valor, use first()
en la suscripción. No tiene que darse de baja si utiliza first()
.
import Injectable from '@angular/core'
import BehaviorSubject from 'rxjs/BehaviorSubject';
@Injectable()
export class NavService
// Observable navItem source
private _navItemSource = new BehaviorSubject(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:`
`
)
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, así 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 de EventEmitter, esta implementación también almacena el seleccionado actualmente navItem
en el servicio, de modo que cuando se crea un componente de observación, pueda recuperar el valor actual mediante una llamada a la API navItem()
, y luego ser notificado de los cambios a través del 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;
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:`
`,
)
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 observables. Aunque el ejemplo es “comunicación entre padres e hijos”, la misma técnica se aplica a 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, de hecho, usar un EventEmitter en un servicio es una mala práctica.
Respuesta original: (no hagas esto)
Ponga el EventEmitter en un servicio, lo que le permite al ObservingComponent suscribirse directamente (y darse de baja) al evento:
import EventEmitter from 'angular2/core';
export class NavService
navchange: EventEmitter = 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:`
`,
)
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 debe darse de baja cuando se destruye
- tenemos que pasar el componente a
subscribe()
para que el adecuadothis
se establece cuando se llama a la devolución de llamada
Actualización: una alternativa que resuelve la segunda viñeta es hacer que el ObservingComponent se suscriba directamente al navchange
Propiedad EventEmitter:
constructor(private navService:NavService)
this.subscription = this.navService.navchange.subscribe(data =>
this.selectedNavItem(data));
Si nos suscribimos directamente, no necesitaríamos el subscribe()
método en NavService.
Para hacer que el NavService esté un poco más encapsulado, puede agregar un getNavChangeEmitter()
método y use eso:
getNavChangeEmitter() return this.navchange; // in NavService
constructor(private navService:NavService) // in ObservingComponent
this.subscription = this.navService.getNavChangeEmitter().subscribe(data =>
this.selectedNavItem(data));