Atualização 27/06/2016: em vez de usar Observables, use
- um BehaviorSubject, conforme recomendado por @Abdulrahman em um comentário, ou
- um ReplaySubject, conforme recomendado por @Jason Goemaat em um comentário
Um Assunto é um Observável (para que possamos subscribe()
fazê-lo) e um Observador (para que possamos chamá next()
-lo para emitir um novo valor). Nós exploramos esse recurso. Um Assunto permite que os valores sejam multicast para muitos Observadores. Não exploramos esse recurso (temos apenas um observador).
BehaviorSubject é uma variante do Subject. Tem a noção de "o valor atual". Exploramos isso: sempre que criamos um ObservingComponent, ele obtém o valor atual do item de navegação do BehaviorSubject automaticamente.
O código abaixo e o plunker usam BehaviorSubject.
ReplaySubject é outra variante do Subject. Se você quiser esperar até que um valor seja realmente produzido, use ReplaySubject(1)
. Enquanto um BehaviorSubject requer um valor inicial (que será fornecido imediatamente), ReplaySubject não. O ReplaySubject sempre fornecerá o valor mais recente, mas como ele não possui um valor inicial necessário, o serviço pode executar algumas operações assíncronas antes de retornar seu primeiro valor. Ele ainda dispara imediatamente nas chamadas subsequentes com o valor mais recente. Se você deseja apenas um valor, use first()
na assinatura. Você não precisa cancelar a inscrição se usar 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
Resposta original que usa um Observable: (requer mais código e lógica do que usar um BehaviorSubject, por isso não o recomendo, mas pode ser instrutivo)
Então, aqui está uma implementação que usa um Observable em vez de um EventEmitter . Diferentemente da minha implementação EventEmitter, essa implementação também armazena o atualmente selecionado navItem
no serviço, para que, quando um componente de observação for criado, ele possa recuperar o valor atual por meio de uma chamada de API navItem()
e ser notificado sobre alterações por meio do 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 também o exemplo do Component Interaction Cookbook , que usa um Subject
além de observáveis. Embora o exemplo seja "comunicação entre pais e filhos", a mesma técnica é aplicável a componentes não relacionados.