Building a Weather Widget using RxJS

Reactive Programming is a very interesting programming paradigm that I started pondering with last week, which ended up in an article on this blog. Today, I will show you how you can write a very simple weather widget with reactive programming, using RxJS. Or, in other words, how to do HTTP calls in reactive programming fashion.

RxJS

RxJS is an acronym for Reactive Extensions for JavaScript. Simply put, it is a set of libraries to do asynchronous and event-based programming using observable collections and iterators. Simple as that. If you are interested in diving into RxJS, which I recommend, you can start with the README. It has plenty of informations and links that you can use to learn Rx.

The UI

After getting (some of) the RxJS syntax and functions under my belt, I tried to go on and implement something super simple. In the past, I have worked with the Open Weather Map’s API, so I said to myself, let’s build a simple weather widget.

The widget that we will be building will look like this:

This is the default (start) state of the widget. The other state is when the used populates the input field with his location. Then, the widget fetches the weather data and shows it:

Very simple, but sufficient for the purpose of this article. As a sidenote, I will not cover the HTML/CSS part of the widget, as it is quite simple and it’s beside the point of this article.

The code

We will use the part of the Open Weather Map API which returns the current weather for a location. First, in our code, we will need to define the endpoint and the API_KEY (to get yours, just register).

Also, make sure you include RxJS in your project, whether via a simple <script></script> tag in your HTML file, or via any package manager that you prefer. We will use the ES6/Babel syntax in this article.

const API_KEY = "YOUR_API_KEY_HERE";
const API_URL = `http://api.openweathermap.org/data/2.5/weather?appid=${API_KEY}`;

Easy as that. Next, we will need to get both of the DOM elements that we need to work with - the input field and the text paragraph where the temperature will be shown:

var weatherText = document.querySelector('#weather');
var searchInput = document.querySelector('#location-input');

Now, this is where Rx kicks in. We want to grab the keyup event on the input field and kick off a HTTP request after it. This is achievable via the Rx.Observable.fromEvent function, which takes the DOM element and event type as arguments:

Rx.Observable.fromEvent(element, 'event-type');

Or, in our specific case, we want to grab the keyup event on the searchInput DOM element:

var searchInputSource = Rx.Observable
  .fromEvent(searchInput, 'keyup')
  .debounce(500);

As you can see, this is really easy with RxJS. As a UX nicety, we use the debounce function, which will make RxJS register events 500ms after the last one is done. Any intermediary events will be rejected. The searchInputSource will be the event source stream. From there, we will map our events, build our request streams and send the HTTP requests to the API.

var requestOnFindStream = searchInputSource.map(ev => {
    return API_URL + "&q=" + ev.target.value;
  });

Now, this section of the code will map all of the events coming through the searchInputSource and build the full request API URLs. At the end of the API_URL it will append a query string with the location specified in the input field.

var responseStream = requestOnFindStream
  .flatMap(requestUrl => {
    return Rx.Observable.fromPromise($.getJSON(requestUrl));
  })
  .map(response => convertToCelsius(response.main.temp))
  .startWith(0);

Now, here’s where all of the magic happens. On the requestOnFindStream we apply the flatMap function, whose role here is to build a stream of new streams. I know, this looks confusing, but bear with me.

In the flatMap function, we wrap any incoming requestUrl from the requestOnFindStream and we create a new Rx.Observable from a jQuery promise. Later on, when we subscribe on the observable (the responseStream), the promise will be invoked and the data will pass through all of the chained functions.

After the flatMap, we map all of the responses that we will get from the resolved promise and we use the convertToCelsius function:

function convertToCelsius(kelvin) {
  return (kelvin - 273.15).toFixed(1);
}

The function is rather simple - it converts Kelvin degrees from Celsius degrees. After that, we return only the converted temperature from the response body. At the end, the startWith functions just sets a default value for the responseStream, so instead of showing a blank widget, we show 0 degrees temperature.

As a last step, we need to subscribe to the requestStream:

responseStream.subscribe(temp => {
  weatherText.innerHTML = `${temp} °C`;
});

This is rather simple - we subscribe to the data stream and set a callback. The callback will be invoked any time we get any data through the stream. It will only update the HTML on the widget, showing the temperature to the user.

If you want to see the source of and/or play with this widget, here it is.

Outro

Although Reactive Programming requires a bit of a mindset switch, I found that programming in the paradigm is quite interesting and challenging. If you want to dig deeper, I would recommend heading over to Egghead.io and checking their courses on Reactive Programming. Also, if you are interested in Reactive Programming with Ruby, check my latest post on RxRuby.

Comments