43

I have two Angular2 components which need to share data via a service:

@Injectable()
export class SearchService {
  private searchResultSource = new Subject<string>()
  searchResult$ = this.searchResultSource.asObservable()

  setSearchResults(_searchResult: string): void {
    this.searchResultSource.next(_searchResult)
  }
}

Suppose ComponentA is rendered and it emits an event via SearchService.setSearchResults. Then the user navigates to ComponentB, which also subscribes to searchResult$. However, ComponentB will never observe the event emitted by ComponentA because it was not subscribed to searchResult$ at the time ComponentA emitted an event, as it did not exist.

How can I create an Observable which emits the last event to every new subscriber?

Liam
  • 27,717
  • 28
  • 128
  • 190
kdu
  • 1,259
  • 4
  • 12
  • 18
  • A comprehensible and up-to-date article explaining the `AsyncSubject`, `BehaviorSubject` and `ReplaySubject` can be found here: https://medium.com/@luukgruijs/understanding-rxjs-behaviorsubject-replaysubject-and-asyncsubject-8cc061f1cfc0 – conceptdeluxe Sep 13 '18 at 00:18
  • I found this to be a clear simple (no angular/TS) explanation about the three Subjects with live code to play with https://coryrylan.com/blog/rxjs-observables-versus-subjects – DKebler Jan 03 '20 at 17:38
  • Also as of rxjs 6 there is shareReply operator see comments here. https://stackoverflow.com/questions/41730542/how-to-convert-an-observable-to-a-replaysubject-in-rxjs, docs here https://www.learnrxjs.io/operators/multicasting/sharereplay.html – DKebler Jan 03 '20 at 17:56

2 Answers2

66

BehaviorSubject immediately emits the last value to new subscribers:

@Injectable()
export class SearchService {

  private searchResultSource = new BehaviorSubject<string>('');

  setSearchResults(_searchResult: string): void {
      this.searchResultSource.next(_searchResult);
  }
}

ReplaySubject emits all previous events to new subscribers.

conceptdeluxe
  • 3,753
  • 3
  • 25
  • 29
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
21

You can use the ReplaySubject to always get the last value of the Observer, something like this :

@Injectable()
export class SearchService {

  private searchResultSource = new ReplaySubject<string>(1);

  setSearchResults(_searchResult: string): void {
      this.searchResultSource.next(_searchResult);
  }
}

And just subscribe as normal.
A more advanced example can be found here : caching results with angular2 http service

royhowie
  • 11,075
  • 14
  • 50
  • 67
Tiberiu Popescu
  • 4,486
  • 2
  • 26
  • 38
  • Can you explain "Subscribe as normal"? Is there any case where subscription doesnt work? – sabithpocker Jun 21 '17 at 16:07
  • I meant that I don't need to show how you subscribe to this ReplaySubject, as you don't need to do anything special on the subscription side, just subscribe and you'll get the last value. – Tiberiu Popescu Jun 21 '17 at 18:40
  • Okay cool. I had a situation where `BehaviorSubject` subsription didnt work but `ReplaySubject` did. Was trying to understand why. – sabithpocker Jun 22 '17 at 07:16
  • 4
    I'm still very new to this Rx stuff and observables (aren't we all at this point?!) but this answer helped me quite a bit. For anyone in my position... I originally chose a `ReplaySubject` (opposed to `BehaviorSubject`) because you don't need to initialize a `ReplaySubject` with a value. But subscribing to a `ReplaySubject` observable by default gives you all values that were broadcast during its lifetime. initialiazing the `ReplaySubject` with a buffer size of 1 like in @tibbus answer worked for me! – default_noob_network Jun 30 '17 at 18:06