87

Being new to RxJS I often create a subject which holds values in the future, but is initially undefined. It can only be undefined the first time. I currently use a filter to skip undefined values, but this is quite cumbersome as I do it everywhere as I need only once. (Maybe I do something wrong here?) Can I somehow subscribe to mySubject only after it got its first value via onNext?

var mySubject = new Rx.BehaviorSubject(undefined);

mySubject.filter(function(value) {
  return value !== undefined;
}).subscribe(function(value) {
  // do something with the value
});
Pipo
  • 5,623
  • 7
  • 36
  • 46
  • 3
    There's also a skip method you can call on the observable to skip any number of leading items in the sequence. – Will Mar 08 '17 at 23:24
  • 1
    @Will - can u pls enlighten me on how to use skip with Subscription to BehaviorSubject ? – Jay Shah May 05 '17 at 15:08

6 Answers6

75

Use new Rx.ReplaySubject(1) instead of BehaviorSubject.

Brandon
  • 38,310
  • 8
  • 82
  • 87
  • 6
    Thank you again. For the third time...? I *do* read the RxJS docs and several guides, but it is sometimes really hard to find a solution for a certain problem. – Pipo Feb 04 '15 at 20:01
  • 1
    Yeah it's a lot to memorize – Brandon Feb 04 '15 at 20:33
  • @Brandon can u pls tell me that how can I initialize the value then ? And I don't need initial value – Jay Shah May 05 '17 at 15:04
  • Works like a charm – Timothy Dalton Jun 23 '17 at 09:02
  • 7
    This is no longer a strong correlation; as of RxJS 5, `ReplaySubject` lost a few aspects of `BehaviorSubject` it held when this question was first answered. For example, if you truly desire a synchronous snapshot value, you are back to using `.skip(1)` (https://stackoverflow.com/a/44884189/608220) or subscribing your own cache. Also note the functionality on completion has changed. – shannon Jan 03 '18 at 02:43
  • @shannon Could you be more specific or link an article that describes the lost aspects? – Jack A. Mar 14 '18 at 16:29
  • @JackA. Per my comment, it lost `getValue()`. Subscribing to a `ReplaySubject()` requires `skip(1)` if you don't want a stale value. Sorry, I don't remember what I meant about `OnComplete()`. I feel like there were one or two smaller things as well. Point is, they are pretty different from each other. – shannon Mar 15 '18 at 19:41
  • 4
    If you don’t want stale values then use Subject. ReplaySubject os for when you *do* want a stale value. And unlike BehaviorSubject, it can be constructed without a starting value – Brandon Mar 15 '18 at 22:00
  • 2
    ReplaySubject(1) works for me without skip. I need it because, I may end up subscribing to subject AFTER .next(...) is called (a quasi-asynchronous function returning a single object via an observable). Maybe I should just be using bare-bones Promise instead? – FizxMike Jan 23 '19 at 14:44
57

As mentioned by Will you should be able to just skip the first value by using the skip operator:

var mySubject = new Rx.BehaviorSubject(undefined);

mySubject.pipe(skip(1)).subscribe(function(value) {
  // do something with the value
});
Shashank Vivek
  • 16,888
  • 8
  • 62
  • 104
lwohlhart
  • 1,829
  • 12
  • 20
  • 32
    The problem with this approach is that it will skip a valid value if the `subscribe` call is made after the first real value is emitted. – Jack A. Mar 14 '18 at 15:42
  • 1
    @JackA. but that is also the good thing about this approach :-) – Simon_Weaver Jul 03 '18 at 00:00
  • 3
    @Simon_Weaver Depends on your use case. If you only want to receive "future" values when you subscribe, then this approach will work; if you want to receive the most recent "past" value when you subscribe, then it will not. Hard to tell which use case the OP wanted, but I definitely wanted the latter when I ran across this. – Jack A. Jul 03 '18 at 16:49
6

mySubject.pipe( skipWhile( v => !v ) );

Billy
  • 341
  • 3
  • 11
  • After applying the `skipWhile`, it becomes an Observable, so it doesn't illustrate a real use case where you need a BehaviorSubject. – Léon Pelletier Apr 03 '18 at 20:54
2

For now I am using the filter operator, but I don't know if it's a good solution:

var mySubject = new Rx.BehaviorSubject().filter(x => !!x);

mySubject.subscribe(value => { /* will receive value from below */);

mySubject.next('value');

mySubject.subscribe(value => { /* also receives the value */ });
danguilherme
  • 642
  • 8
  • 23
  • After applying the `filter`, it becomes an Observable, so it doesn't illustrate a real use case where you need a BehaviorSubject. – Léon Pelletier Apr 03 '18 at 20:53
1

I have found this frustrating in both RxJS and RxSwift. (Wanting a value subject combined with the ability to wait on the first value).

For JS I am currently just tucking away a filtered version in the subject, something like this:

    let mySubject = new Rx.BehaviorSubject();
    mySubject.wait = mySubject.pipe(filter(v=>v!==undefined));

So the subject is still exposed for publishing but clients don't have to repeat the filter.

    mySubject.wait.subscribe((v)=>{...}); 
Pat Niemeyer
  • 5,930
  • 1
  • 31
  • 35
0

Sometimes there will be a need for behaviourSubject, where the initial value does not matter and the current value is needed asynchronously while working inside a stream, Just in our case multiple chain promises are handled with user cancel while processing or during fetching the data from anywhere inside the stream.

This can be achieved using the following way.

// for user related commands
this.commandSource = new BehaviorSubject(CONTINUE);
// filtering over initial value which is continue to make it as a different pipe
const stopPipe = commandSource.pipe(filter(val => val === STOP));
const fetchStream = Observable.fromPromise(this.fetchDetails);

merge(fetchStream, stopPipe).pipe(
 take(1),
 takeWhile(() => commandSource.value === CONTINUE),
 concatMap((response) => {
  // fetch Another response you can return promise directly in concatMap
  // return array of response [1 ,2 ,3];
  return this.fetchYetAnotherDetails;
 }),
 // we can add this to stop stream in multiple places while processing the response
 takeWhile(() => commandSource.value === CONTINUE),
 // triggers parallelly values from the concatMap that is 1, 2 , 3
 mergeMap(() => // massage the response parallelly using )
 finalize(() => thi
  commandSource.complete())
).subscribe(res => {
 // handle each response 1, 2, 3 mapped
}, () => {
 // handle error
}, () => {
 // handle complete of the stream
});

// when user, clicks cancel, this should stop the stream.
commandSource.next(STOP)
Mukundhan
  • 3,284
  • 23
  • 36