Search code examples
rxjsrxjs5

How to use Rx.Observable.fromEvent in an element that does not exist in the DOM yet?


With regular JS and Jquery you can do something like $(document).on('click', '#id', function () { // whatever }). This allows us to add an event in a element that may not exist in the DOM yet. I would like to use Rx.Observable.fromEvent(ele, 'click') to do the same - I need to do that because the elements are being created in another stream. Is this possible?


CONTEXT

Some context of what I am trying to achieve. I'm following The introduction to Reactive Programming you've been missing. The proposed exercise is:

  1. Fetch users from GitHub API
  2. Display three users as suggestions to follow;
  3. Each user should have a "remove" button;
  4. Whenever a remove button is clicked, remove that suggestion and load another one from the buffer;

It goes very well until the part of loading the three suggestions. For example:

Rx.Observable
    .fromEvent($('#refresh'), 'click')
    .startWith('start up click')
    .map(() => Math.floor(Math.random() * 500))
    .map(offset => `${GITHUB_API}?since=${offset}`)
    .map(url => jQuery.getJSON(url))
    .flatMap(promisse => Rx.Observable.from(promisse))
    .map(users => users.slice(0, 3))
    .do(_ => $('#users').empty())
    .flatMap(users => users)
    .map(user => createItem(user))
    .subscribe(user => $('#users').append(user));

// Just an example for creating the elements
function createItem(user, idx) {
  return $(`
    <li class="item-wrapper horizontal" >
      <img src="${user.avatar_url}" class="rounded-circle" width="50" height="50">

      <div class="item-infos-wrapper vertical">
        <span class="font-weight-bold">${user.login}</span>
        <span class="font-weight-normal">What can we put in here?</span>
      </div>

      <button id="close${idx}" type="button" class="btn btn-outline-danger btn-sm item-action">remove</button>
    </li>
  `);
}

From what I understood of the aforementioned article, the idea to handle the item removal is to use combineLatest with a stream of "remove button clicked" and "requestStream" (containing the results of the API), feeding the combined data in the pipe that creates the elements. My current problem is build this retro feeding from the remove button clicked stream plus request stream into the consumer that creates the elements.


Solution

  • You could do something like this but it's not clear to me where idx comes from. Is it a property of the user?

    Rx.Observable
        .fromEvent($('#refresh'), 'click')
        .startWith('start up click')
        .map(() => Math.floor(Math.random() * 500))
        .map(offset => `${GITHUB_API}?since=${offset}`)
        .map(url => jQuery.getJSON(url))
        .flatMap(promisse => Rx.Observable.from(promisse))
        .map(users => users.slice(0, 3))
        .do(_ => $('#users').empty())
        .flatMap(users => users)
        .map(user => createItem(user)) //at this point we have a listItem in the stream
    
        // from this point it's changed
    
        .tap(listItem => $('#users').append(listItem)) //append to users, but keep the listItem in the stream
        .map(listItem => $(listItem).find('button')[0]) //get the button element
        .mergemMap(closeButton => fromEvent(closeButton, 'click')) //subscribe to click on the button and merge all the clicks into one stream
        .subscribe((event: MouseEvent) => {
            console.log('clicked', event); //a close button was clicked...
        });
    

    and here is a way of removing the li when the button is clicked:

    Rx.Observable
        .fromEvent($('#refresh'), 'click')
        .startWith('start up click')
        .map(() => Math.floor(Math.random() * 500))
        .map(offset => `${GITHUB_API}?since=${offset}`)
        .map(url => jQuery.getJSON(url))
        .flatMap(promisse => Rx.Observable.from(promisse))
        .map(users => users.slice(0, 3))
        .do(_ => $('#users').empty())
        .flatMap(users => users)
        .mergeMap(user => {
            const listItem = createItem(user);
            $('#users').append(listItem);
    
            const closeButton = $(listItem).find('button')[0];
            return Rx.Observable.fromEvent(closeButton, 'click').first().tap(_ => listItem.remove());
        })
        .subscribe();