Angular services with RxJS; decrapify your services.

Alright, here’s the deal. Services/singletons are kind of a strange pattern to me. They’re only made once, so they’re great for keeping data consistent across multiple components/controllers, but a lot of the time they end up just being what’s essentially a library of static API calls.

So, what if, instead of thinking of a service as an object that lets you make API calls, we thought of it as a stream that you can put information into and take information out of. That way, the service can live in it’s own world, and anyone who’s listening can get nearly instant information without worrying about cacheing woes or polling.

This, as you might have guessed as you might have guessed by the title, leads us to RxJS. RxJS is a

is a set of libraries to compose asynchronous and event-based programs using observable collections

as taken from their Github. What’s this actually mean? It’s a badass library that let’s you deal with changes to objects in real time, without needing to deal with listeners or callback hells. LET’S DO THIS. I’ll make a UserService as an example.

var rx = require('rx');function UserService() {}

Great, we have rx imported and a basic object. Let’s start by making an observable for this lovely object, and a method that returns a fake user.

var colors = [
‘red’,
‘orange’,
‘yellow’,
‘green’,
‘blue’,
‘indigo’,
‘purple’
];
var UserService = function() {
var self = this;

/* Variables */
self.user = new rx.Subject();

/* Functions */
self.refresh = refresh;

function refresh() {
self.user.onNext({
name: ‘Fake Name’,
favoriteColor: colors[Math.floor(Math.random() * colors.length)]
});
}
}

Cool, now let’s see what happens when we subscribe to the user and refresh a few times;

var userService = new UserService();userService.user.subscribe((user) => {
console.log('user updated:', user);
});
userService.refresh();
userService.refresh();

What you should see is something along the lines of

> user updated: { name: ‘Fake Name’, favoriteColor: ‘yellow’ }
> user updated: { name: ‘Fake Name’, favoriteColor: ‘purple’ }

Nice. So what does this mean? It means that by subscribing to the userService singleton, we can get updates when anyone updates it. If you have, say, a menu bar that has the user’s name in their favorite color, for example, and the user updates their favorite color in the settings, if the menu is subscribed to the user service (which it should be…), then it’ll immediately get the updates that the settings page has made and change the color. Nifty, right?

What would that look like, you ask? Something like this;

function someAsynchronousServerUpdate(user, callback) {
if (user.favoriteColor !== 'purple') {
callback(new Error('y u no like purple?!'));
} else {
callback(null, user);
}
}
var UserService = function() {
var self = this;

/* Variables */
self.user = new rx.Subject();

/* Functions */
self.refresh = refresh;
self.update = update;

function refresh() {
self.user.onNext({
name: ‘Fake Name’,
favoriteColor: colors[Math.floor(Math.random() * colors.length)]
});
}
function update(_user) {
someAsychronousServerUpdate(_user, (err, _validatedUser) {
if (err) {
// Do your error handling here
} else {
self.user.onNext(_validatedUser)
}
}
}
}

So now we have validation built in. Coolio. There’s an issue though. Right now, when we subscribe to the user on the userService, we don’t get a user back until someone either updates or calls refresh. That’s a little odd, but easily fixable, instead of using a normal Subject, we’ll use a BehaviorSubject.

BehaviorSubjects represent “a value that changes over time.” Sounds a lot like a user that can be updated at any given time huh? So we change around our code to supply the initial value, and then refresh as soon as it’s instantiated. If anyone happens to subscribe before we get a user back, then they’ll get null, and the next object will be the first result of fetching the user, and anyone who subscribes after that will just get the last version of user to be fetched.

function someAsynchronousServerUpdate(user, callback) {
if (user.favoriteColor !== 'purple') {
callback(new Error('y u no like purple?!'));
} else {
callback(null, user);
}
}
var UserService = function() {
var self = this;

/* Variables */
self.user = new rx.BehaviorSubject(null);

/* Functions */
self.refresh = refresh;
self.update = update;
/* Initialization Logic */
self.refresh();

function refresh() {
self.user.onNext({
name: ‘Fake Name’,
favoriteColor: colors[Math.floor(Math.random() * colors.length)]
});
}
function update(_user) {
someAsychronousServerUpdate(_user, (err, _validatedUser) {
if (err) {
// Do your error handling here
} else {
self.user.onNext(_validatedUser)
}
}
}
}

These observable streams are very powerful and will also allow you to do things like mapping and filtering on the fly. In a world where all of our UI is reactive, why not make our data and services reactive as well.

Cofounder of https://droppod.gg, current Google employee.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store