0

As shown below, why does example 1 fail when we return the div and then separately return a new click observable flatMap to click$ ?

Example 2 works fine. JSBin below to try it out

Anyone could explain why this happens? From what I understand, flatMap expands Observable. http://jsbin.com/sowodi/3/edit?js,console,output

// Example 1
() => {
  let stream = Rx.Observable.fromArray([1, 2, 3]);

  let div$ = stream.map(i => {
    let div = document.createElement('div');
    div.innerHTML = `NOT WORKING DIV ${i}`;
    return div;
  })

  div$.subscribe(div => {
    document.querySelector('body').appendChild(div);
  })

  let click$ = div$.flatMap(
    div => Rx.Observable.fromEvent(div, 'click')
  );

  click$.subscribe(click => console.log('click'));
}();


// Example 2
() => {
  let stream = Rx.Observable.fromArray([4, 5, 6]);

  let click$ = stream.flatMap(i => {
    let div = document.createElement('div');
    div.innerHTML = `DIV ${i}`;
    document.querySelector('body').appendChild(div);

    return Rx.Observable.fromEvent(div, 'click');
  })

  click$.subscribe(click => console.log('click'));
}();
<!DOCTYPE html>
<html>

<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.6/rx.all.js"></script>
  <meta charset="utf-8">
  <title>JS Bin</title>
</head>

<body>
  <div id="app">
    THE APP ID
  </div>
</body>

</html>
FeliciousX
  • 38
  • 3
  • I don't think RxJs guarantees that the "append" subscription would run before the flatMap callback in this scenario. Have you tried adding logging to both to confirm? – Richard Szalay Feb 05 '16 at 06:43
  • 1
    I tried and found out that append always runs before flat mapping. I also think the order don't matter? As you can hook an `onClick` event before appending to DOM – FeliciousX Feb 05 '16 at 07:02

1 Answers1

0

It is because in your first example you are creating two sets of divs. You have done the equivalent of this:

  Rx.Observable.fromArray([1, 2, 3])
    .map(i => {
      let div = document.createElement('div');
      div.innerHTML = `NOT WORKING DIV ${i}`;
      return div;
    }).subscribe(div => {
      document.querySelector('body').appendChild(div);
    })

  Rx.Observable.fromArray([1, 2, 3])
    .map(i => {
      let div = document.createElement('div');
      div.innerHTML = `NOT WORKING DIV ${i}`;
      return div;
  }).flatMap(
      div => Rx.Observable.fromEvent(div, 'click')
  ).subscribe(click => console.log('click'));

For the why, it is because fromArray creates a cold Observable meaning that each subscription will create a brand new independent which will not affect any other streams that you create.

There are two ways you can fix it, 1) which you already discovered is to create only a single stream to house all of your logic or 2) alternatively you can make the result from div$ into a hot Observable so that all subscriptions will actually share the same stream. This will come with its own set of issues, namely that you can now miss messages if you are not careful, but to refactor your first case it would end up looking like:

  let stream = Rx.Observable.fromArray([1, 2, 3]);

  let div$ = stream.map(i => {
    let div = document.createElement('div');
    div.innerHTML = `NOT WORKING DIV ${i}`;
    return div;
  })
  //Make div$ into a connectable Observable so the subscriptions will
  //share the underlying Observable
  .publish()

  div$.subscribe(div => {
    document.querySelector('body').appendChild(div);
  })

  let click$ = div$.flatMap(
    div => Rx.Observable.fromEvent(div, 'click')
  );

  click$.subscribe(click => console.log('click'));

  //Nothing happens until you call connect which subscribes to the underlying
  //Observable
  div$.connect();
paulpdaniels
  • 18,395
  • 2
  • 51
  • 55