Intro to Rx

An example of a "Who to follow" suggestions box built with the Reactive Programming library RxJS.

HTML

<div class="container">
    <div class="header">
         <h2>Who to follow</h2><a href="#" class="refresh">Refresh</a>
    </div>
    <ul class="suggestions">
        <li class="suggestion1">
            <img />
            <a href="#" target="_blank" class="username">this will not be displayed</a>
            <a href="#" class="close close1">x</a>
        </li>
        <li class="suggestion2">
            <img />
            <a href="#" target="_blank" class="username">neither this</a>
            <a href="#" class="close close2">x</a>
        </li>
        <li class="suggestion3">
            <img />
            <a href="#" target="_blank" class="username">nor this</a>
            <a href="#" class="close close3">x</a>
        </li>
    </ul>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.2.26/rx.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.2.26/rx.async.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.2.26/rx.coincidence.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.2.26/rx.binding.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.2.26/rx.time.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs-dom/2.0.7/rx.dom.js"></script>

CSS

body {
    font-family: sans-serif;
    padding: 10px;
}
h2 {
    font-weight: bold;
    display: inline-block;
}
.refresh {
    font-size: 80%;
    margin-left: 10px;
}
.header {
    background: #ECECEC;
    padding: 5px;
}
.suggestions {
    border: 2px solid #ECECEC;
}
li {
    padding: 5px;
}
li img {
    width: 40px;
    height: 40px;
    border-radius: 20px;
}
li .username, li .close {
    display: inline-block;
    position: relative;
    bottom: 15px;
    left: 5px;
}

JavaScript

const buttonStream = selector => Rx.Observable.fromEvent(document.querySelector(selector), 'click')

const refreshClickStream = buttonStream('.refresh')
const close1ClickStream = buttonStream('.close1')
const close2ClickStream = buttonStream('.close2')
const close3ClickStream = buttonStream('.close3')

const requestStream = refreshClickStream.startWith('startup click')
  .map(() => 'https://api.github.com/users?since=' + Math.floor(Math.random() * 500))

const responseStream = requestStream
  .flatMap((requestUrl) => Rx.Observable.fromPromise(fetch(requestUrl).then(res=>res.json())))

function createSuggestionStream(closeClickStream) {
  return closeClickStream.startWith('startup click')
    .combineLatest(responseStream,
      (click, listUsers) => listUsers[Math.floor(Math.random() * listUsers.length)]
    )
    .merge(
      refreshClickStream.map(() => null)
    )
    .startWith(null)
}

const suggestion1Stream = createSuggestionStream(close1ClickStream);
const suggestion2Stream = createSuggestionStream(close2ClickStream);
const suggestion3Stream = createSuggestionStream(close3ClickStream);


// Rendering ---------------------------------------------------
function renderSuggestion(selector) {
  const suggestionE = document.querySelector(selector);
  const usernameE = suggestionE.querySelector('.username');
  const imgE = suggestionE.querySelector('img');
  return suggestedUser => {
    if (suggestedUser === null) {
      suggestionE.style.visibility = 'hidden';
    } else {
      suggestionE.style.visibility = 'visible';
      usernameE.href = suggestedUser.html_url;
      usernameE.textContent = suggestedUser.login;
      imgE.src = "";
      imgE.src = suggestedUser.avatar_url;
    }
  }
}

suggestion1Stream.subscribe(renderSuggestion('.suggestion1'));
suggestion2Stream.subscribe(renderSuggestion('.suggestion2'));
suggestion3Stream.subscribe(renderSuggestion('.suggestion3'));