I’ve been learning a lot of Reactive Extensions (Rx) lately, and in the spirit of learning I decided to share some initial thoughts on the concepts and give a very light overview. Rx is very deep, but hopefully this article gives you an initial understanding and examples to solidify the knowledge.
If you haven’t heard of Rx before, I highly recommend reading Erik Meijer’s blog post in which he describes an alternate way of thinking about events. In short, he describes events (mouse clicks, keyboard events) as a stream of data. Think of an array where you have a list of items but with the added element of time in between each item. For example, as your mouse moves it creates a stream of coordinates. (The dots represent time passing)
mouseMoves = [….{x: 45, y: 45}…….{x: 45, y: 50}…..{x: 50, y: 50}…]
See, mouseMoves is just an array with time between the items. This is what is known as an Observable.
Once we have an observable, we can do all kinds of things to the stream of data. We can filter it for certain items, we can map over each data item and transform it to something else, we can merge it with another Observable to create a new data stream. The documentation for the javascript implementation, gives you just a taste of what you can do.
A Practical Example – Mouse Moves
Let’s say that we want to add a listener to our page that detects whenever the mouse hovers over an image tag. In addition, while we want to know whenever the mouse is hovering over the image, we also want to throttle the number events actually occurring. The following Rx code accomplishes this in just a few lines of code.
Let’s take this line by line to see just what is happening.
After querying the page for a list of image tags, we create two new Observables by attaching them to the images DOM list. These observables are now listening to all mouseover and mouseout events.
// Listen to the stream of mouseover events var mouseOvers = Rx.Observable.fromEvent(images, 'mouseover'); // Listen to the stream of mouseout events var mouseOuts = Rx.Observable.fromEvent(images, 'mouseout');
Now that we are listening to events, we want to filter these events to only return every 300ms (as opposed to very fast which it would do right now) and only while we are still hovered over an image. Here’s how this code works. We loop over the list of mouseover events streaming in using ‘map’. For each event we occur, we create a new Observable that is delayed by 300ms. And lastly, we filter that nested observable with the ‘takeUntil‘ operator so that we only return a valid event if the mouse has not left the image. At this point, the event is then returned to the outer ‘map‘ result for the final operator, ‘switchLatest.’ Switch latest takes in a stream of events and returns only the most recent event. This operator is especially useful for ajax calls, where you may have made a few requests, but only care about the data from the most recent request made. Likewise, this returns the most recent mouseover event that was valid.
// Create an Observable for all mouseOvers var imageHover = mouseOvers. // For each (map) mouseOver event map(function(e) { // Wrap the event with an another Observable, so we can throttle it return Rx.Observable.return(e.target). // Wait to see if the mouseOver is still on this same boxart delay(300). // Don't return anything if a mouseout occurred, this stops the observable from returning anything else takeUntil(mouseOuts); }). // Return the last throttled event switchLatest();
Now that we’ve created our observable with its propers filers, we simply ‘forEach‘ or ‘subscribe’ over the stream as if it were an array.
// Subscribe to the img hovers imageHover.subscribe(function(x) { console.log('Mouse over image with alt tag: ' + x.alt); });
That’s it! We can now listen for mouseover events on images like an array.
This is just scratching the surface of Rx and we haven’t even touched on how Observables automatically unsubscribe themselves from events or handle errors. If you’d like to learn and play more with observables, I highly recommend giving Jafar Husain’s interactive tutorial a try.
isn’t a part of what you’re doing solved more easily by using “debounce”?
Absolutely. The example is simply to illustrate a potential use case and how it can be extended to more complex interactions.