-1

Two buttons on screen with clicks plumbed in like this.

  b1c: number;
  b1s: Subject<number>;
  o1: Observable<number>;
  b2c: number;
  b2s: Subject<number>;
  o2: Observable<number>;


  rxjs(): void {
    this.b1c = 0;
    this.b1s = new Subject<number>()

    this.b2c = 0;
    this.b2s = new Subject<number>()

    this.b1s.pipe(
      tap (z => console.log('do', z)),
      mergeMap(z1 => this.b2s.pipe(
        map(z2 => `${z1} / ${z2}`)
      ))
    ).subscribe(z => console.log(z));

    // When you click button 1 nothing happens.
    // When you click button 2 you get output i/j for all values of i from 0 to current, and the current value of j.
    // This is incorrect because this.b1s is not a ReplaySubject and therefore should not remember the previous values.
  }

  buttonClick(buttonNumber: number) {
    if (buttonNumber === 1) {
      this.b1s.next(this.b1c);
      this.b1c++;
    }
    else {
      this.b2s.next(this.b2c);
      this.b2c++;
    }
  }

Can you explain this behaviour? (Button clicks: 1, 1, 1, 1, 2, 1, 1, 1, 2.)

enter image description here

It should be only

5 / 1
6 / 1
7 / 1

because 0 to 4 have already been consumed; there is no reason why they should be remembered.

In fact maybe there should be no output at all because at no time do the two observables simultaneously fire -- you can't click two buttons at once.

How do you explain this behaviour, and how is it possible to deduce this from the documentation?

Furthermore: I don't understand why there are three 30s and three 50s and why they're mixed up. There should be six outputs becuase there are six events. https://rxjs-dev.firebaseapp.com/api/operators/flatMap And we still have this business about it remembering the last value from the other source.

And what on earth is

10*i--10*i--10*i-|

supposed to mean?

Richard Barraclough
  • 2,625
  • 3
  • 36
  • 54
  • In your code you have `mergeMap` not `switchMap` as you say in the title – martin Jan 26 '21 at 10:06
  • Edited; although `switchMap` is also completely impenetrable. – Richard Barraclough Jan 26 '21 at 10:08
  • 1
    Every time you click `b1s` you're merging another Observable into the chain. So by the time you click `b2s` you've already merge `b2s` 5 times so it's going to make 5 emissions. – martin Jan 26 '21 at 10:10
  • It exactly how [mergeMap](https://rxjs-dev.firebaseapp.com/api/operators/mergeMap) work.It merge a recently fired value from b1s, to a serious of values fired from b2s( which remembered by mergeMap). The first click on button 1 isnt doing anything, because nothing fired from b2s yet. It has nothing to do with Replay Subject or Behavior Subject – Ethan Vu Jan 26 '21 at 10:14
  • 1
    Regarding the marble syntax see https://stackoverflow.com/q/64288479/9423231 – frido Jan 26 '21 at 14:10

1 Answers1

1

What you understand as 'remembering history' are just active subscriptions.

obs1.pipe(mergeMap(v => obs2)) maps each value from the source (obs1) to a stream (obs2). So you end up with multiple obs2 streams having an active subscription at the same time. These obs2 streams are merged in one output stream and run until they complete, error or you unsubscribe.

In your case obs2 = b2s is a Subject which is a hot observable that doesn't terminate on its own. The final observables behaves like this:

this.b1s.pipe(
  mergeMap(z1 => this.b2s.pipe(
    map(z2 => `${z1} / ${z2}`)
  ))
).subscribe(z => console.log(z));


b1s:    --0--1--2--3--4-------5--6--7--------
b2s-7:    │  │  │  │  │       │  │  └--7/1---
b2s-6:    │  │  │  │  │       │  └-----6/1---
b2s-5:    │  │  │  │  │       └--------5/1---
b2s-4:    │  │  │  │  └--4/0-----------4/1---  
b2s-3:    │  │  │  └-----3/0-----------3/1---  
b2s-2:    │  │  └--------2/0-----------2/1---  
b2s-1:    │  └-----------1/0-----------1/1---  
b2s-0:    └--------------0/0-----------0/1---

output: -----------------0/0-----------0/1---
                         1/0           1/1
                         2/0           2/1 
                         3/0           3/1
                         4/0           4/1
                                       5/1
                                       6/1
                                       7/1

The subscriptions to b2s-0 - b2s-4 are still active when b2s emits 1.

If you don't want the inner streams to run indefinitely you have to terminate them somehow. You could use take(1) if you only want them to emit one value.

this.b1s.pipe(
  mergeMap(z1 => this.b2s.pipe(
    take(1), // <-- only emit 1 value and then complete
    map(z2 => `${z1} / ${z2}`)
  ))
).subscribe(z => console.log(z));


b1s:    --0--1--2--3--4-------5--6--7--------
b2s-7:    │  │  │  │  │       │  │  └--7/1|
b2s-6:    │  │  │  │  │       │  └-----6/1| 
b2s-5:    │  │  │  │  │       └--------5/1|
b2s-4:    │  │  │  │  └--4/0|  
b2s-3:    │  │  │  └-----3/0|  
b2s-2:    │  │  └--------2/0|  
b2s-1:    │  └-----------1/0|  
b2s-0:    └--------------0/0|  

output: -----------------0/0-----------5/1---       
                         1/0           6/1 
                         2/0           7/1 
                         3/0           
                         4/0           

In the documentation obs2 = 10----10----10-| is a cold observable that terminates after 3 emissions. It also generates the same 3 emissions for every subscriber (unlike your Subject).

ob1:    --1-----------------------------3--------------5----------------------------|
obs2-3:   │                             │              └10*5------10*5------10*5-|
obs2-2:   │                             └10*3------10*3------10*3-| 
obs2-1:   └10*1------10*1------10*1-|

output: ---10*1------10*1------10*1------10*3------10*3-10*5-10*3-10*5------10*5----|
  =     --- 10 ------ 10 ------ 10 ------ 30 ------ 30 - 50 - 30 - 50 ------ 50 ----|
frido
  • 13,065
  • 5
  • 42
  • 56
  • Every time I click b2, b1 replays its entire history. Why? This is completely unexpected. And why doesn't clicking b1 output anything when both streams have non-empty histories? Suppose the current values are `b1:10` and `b2: 100`. Then clicking b1 should output `11 / 100` and clicking `b2` should output `10 / 101`. – Richard Barraclough Jan 28 '21 at 09:28
  • @RichardBarraclough `b1` doesn't replay its history. When `b1` emits this value gets mapped to `b2.pipe(map(z2 => '${z1} / ${z2}'))`. This results in a subscription to `b2.pipe(map(z2 => '${z1} / ${z2}'))` but this inner observable doesn't emit anything directly because `b2` doesn't replay its history. So you end up with multiple subscriptions to `b2.pipe(map(z2 => '${z1} / ${z2}'))` (as many as the times `b1` emits). When `b2` emits `b2.pipe(map(z2 => '${z1} / ${z2}'))` emits. So for every active subscription to `b2.pipe(map(z2 => '${z1} / ${z2}'))` you get an output. – frido Jan 28 '21 at 11:05