Saltar al contenido

Delegación: EventEmitter u Observable en Angular

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 adecuado this 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));

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *