Click Me! Written with RxJS
Last weekend I needed to mess around with RxJS, so I tried to write a game with a button that runs away from the cursor. In this post I'll show what I used to write the app, and go through the process step by step.
If you've worked with this technology before, you probably won't learn anything new. This post is aimed at people like me, who from RxJS know only JS đ
The game will be written in TypeScript. There won't be much difference from regular JS, but it's still worth looking at its documentation to know how to declare types of variables and return values from functions.
Why TypeScript
I've been wanting to try it for a long time, and RxJS is written in it. I thought, why not add restrictions and headaches to myself, so here we are ÂŻ\_(ă)_/ÂŻ
What's RxJS
RxJS is an implementation of ReactiveX for JS. ReactiveX in turn, according to their site, is an API for asynchronous work with observable streams. It took me a while to understand it, so let's look into it one by one.
Primitively speaking, a stream is a sequence of something: events, data, transformations, etc. Imagine a chat room where you're chatting with someone. The sequence of messages in it is a stream.
A stream is observable if we can subscribe to itâdeclare a function that will handle each new item. A stream is an observable stream if we sit and read each new message.
The benefit of streams is that they make it possible not to handle, for example, events one by one, but to combine them and work with a set of events at once.
Observer and Observable
ReactiveX fundamentally uses the âObserverâ pattern. The two main concepts we will need are observer and observable.
The observable would be a stream: it sends elements from some source one at a time. This can be thought of as a river on which the âelement shipsâ are floating.
The observer is the object that knows what to do with the elements from the stream and how to handle them. This can be thought of as a child who wants to collect the ships and take them home.
The observer is connected to the observable through a subscription, a function that passes elements from the observable to the observer. It is like a net that catches ships in the river. When a new ship enters the net, the child notices it and can pick it up.
The observable stream knows how to notify about:
- a new item;
- an occurred error;
- items have run out.
To all of this the observer can respond in some way.
Diagrams
To understand the concept of streams better, the RxJS documentation offers so-called marble diagrams. They depict balls, which seem to be strung on a rope.

These balls are the elements in the stream. A stream is a line of time pointing from left to right. If an item is to the left, that means it appeared earlier.
(It would of course be cooler to show all this by animation: you know, like elements falling down one by one, going through a transformation, falling further down.)
Describe the Game
To write a game, we have to define what events we are going to handle and what we want to do with them.
We will monitor mouse movement and check where the cursor is. If it is within 15 pixels of the button, we will redraw the button.
A little closer to the code, we will have a stream of mouse movement events. We'll clean them up and leave only the coordinates {x, y}
. Then we'll filter the coordinates by checking if the cursor is close enough to the button:
Let's Code
In RxJS you can make a stream from anything: from an array, a promise, events in a browser. For example, you can make it from an array using the from
operator:
import { from } from "rxjs";
// Extracts one element at a time out of the array until they run out.
const arraySource = from([1, 2, 3, 4, 5]);
The source for our stream will be the mouse movement event on the screen. To create a source from a browser event, we will use fromEvent
:
import { fromEvent } from "rxjs/observable/fromEvent";
const source = fromEvent(document, "mousemove");
Now the browser event mousemove
will be tracked within document
, and for each move a new element will appear in source
.
We will transform and filter these elements. After each transformation we will get a new observable
with elements that we can interact with again somehow.
Operators
Operators are functions that can transform elements after observable
has sent them.
To apply several transformations one by one, we need pipe
. This is a method that deals with the composition of operators, that is, it applies them in order.
import {map, filter} from 'rxjs/operators'
// ...
const observable = source.pipe(
map(...),
filter(...)
)
The map
operator is needed to apply some function to each element.
We want to extract from each event the coordinates of the mouse on the screen. So in map
we will pass a function that will retrieve this data and return the object.
map((event: MouseEvent): MouseCoords => ({ x: event.x, y: event.y }));
MouseCoords
is the data type we will create to handle coordinates. It will be an object with fields {x, y}
. It's not necessary to create a new type, but it's easier with the type to understand what we're working with.
type MouseCoords = {
x: number;
y: number;
};
// ...
map((event: MouseEvent): MouseCoords => ({ x: event.x, y: event.y }));
The filter
operator will select events that fit the condition we need.
The event suits us if the cursor is within 15 pixels of the button on both axes.
const shouldUpdateApp = ({ x, y }: MouseCoords): boolean => {
const { top, left, widthRange, heightRange } = state.get();
const padding = 15;
return (
inRange(x, left - padding, widthRange + padding) &&
inRange(y, top - padding, heightRange + padding)
);
};
// ...
filter(shouldUpdateApp);
And then the observable
code will look like this:
const source = fromEvent(document, "mousemove");
const observable = source.pipe(
map((event: MouseEvent): MouseCoords => ({ x: event.x, y: event.y })),
filter(shouldUpdateApp),
);
Event Subscription
Every element in the stream tends to get to subscribe
, where it will be handled somehow.
The subscribe
method takes three argument functions. The first function handles new elements, the second handles an error if one occurs, and the third handles the end of the stream:
observable.subscribe(
// `onNext` is called when new elements appear, `el` is the new element.
(el) => {},
// `onError` is called if an error occurs, `er` is the error object.
(er) => {},
// `onCompleted` is called when the stream is finished.
() => {},
);
When a new item appears, we will call the updateApp
function, which will generate random coordinates for the button, update the state of the application and redraw the button:
observable.subscribe(() => updateApp());
const updateApp = () => {
const { left, top } = getNewPosition();
state.update({ left, top });
applyStyle(button, {
left: `${left}px`,
top: `${top}px`,
});
};
Results
I will not elaborate on the class that controls the state of the application and the helper functions. You can look at the source code of the whole thing on GitHub.
The game itself is very simple, although it's ok to get acquainted with RxJS.
Of course, there's a lot more stuff I haven't told you about: creation of threads from promises, Subject
, Scheduler
, lots of operators, which sometimes you can't figure out without special service. But it will do for starters.
Resources
Observer Pattern and Functional Reactive Programming
- Observer on Wiki
- Observer on GitHub
- The introduction to Reactive Programming you've been missing
- RxJS in 5 minutes!