Fragmented Thought

An example converting angular 1.5 to use RxJS

By

Published

Lance Gliser

Heads up! This content is more than six months old. Take some time to verify everything still works as expected.

I've been doing some work recently upgrading massive portions of an Angular 1.3 code base in preparation to move to Angular 2 (someday). From a high level stand point, here's the what can be done ahead of time to help make it easier when you do go:

  • Upgrade to 1.5.x
  • Learn to write using one way data flow as often as possible. I'd suggest using a more functional approach as well, but that's just because it's easier to debug.
  • Start rewriting as much $scope style work as you can into $ctrl components.
  • Make yourself familiar with RxJS observable, and treat anything coming from your api like it's an observable instead of a promise.

Today's snippets are about that last point of the conversion. You can still do it the old way, but Rx ships with Angular 2. You're eventually going to run into one of the many reasons you would be better off using it. There are many more, but here's a couple of my favorite parts.

  • It makes handling async events like user input a breeze: draganddrop.js
  • It can often take less code to achieve the same thing.
  • It makes handling socket based push style additions from your service layer a no-brainer.

So to that point, I've prepared to codepen's that act much like a fancy new component in a legacy system would. Yes, the api is terrible, no you wouldn't be allowed to change until everything could be fixed. I'm simulating legacy awful at that layer, accept it and move on. The entire component is terribly over simplified, but I want to make sure you focus on the RxJS bits.

First, the promise style:

<p data-height="435" data-theme-id="light" data-slug-hash="GjXpNE" data-default-tab="js,result" data-user="lancegliser" data-embed-version="2" class="codepen" >See the Pen <a href="https://codepen.io/lancegliser/pen/GjXpNE/"> Angular example - Pull paged api, stitch, filter, sort, and display data </a> by Lance Gliser (<a href="http://codepen.io/lancegliser">@lancegliser</a>) on <a href="http://codepen.io">CodePen</a>. </p> <script async src="//assets.codepen.io/assets/embed/ei.js"></script>

Then the RxJS solution:

<p class="codepen" data-default-tab="js,result" data-embed-version="2" data-height="445" data-slug-hash="jrvVgz" data-theme-id="light" data-user="lancegliser" >See the Pen <a href="https://codepen.io/lancegliser/pen/jrvVgz/"> Angular &amp; RxJS example - Pull paged api, stitch, filter, sort, and display data</a> by Lance Gliser (<a href="http://codepen.io/lancegliser">@lancegliser</a>) on <a href="http://codepen.io">CodePen</a>. </p>

The Rx bits

(Behavior) subjects

Subjects are used to wire in control of your stream. You can cause updates at any part required, and the rest of the stream will reprocess. filterSubject is being used here to allow in memory filtering based on keyboard input. A couple notes:

  • BehaviorSubject allows to cache the last result, in this case 'skywalker'. Any future subscriptions (like ours in this example) automatically receive the last broadcast.
  • BehaviorSubject could have taken an initial argument. I separated it just to make the .next call in the controller to be more obvious. This would be more useful in the case of something like a component binding being the initial filter.
  • var filtered$ = stitched$.combineLatest(filter\$, \_filter); is able to be prepared ahead of time due to the nature of Rx. The stream is composed, but not activated until there is at least one end subscriber.
var filterSubject = new Rx.BehaviorSubject(); var filter$ = filterSubject.debounceTime(150); // ... $ctrl.filter = "skywalker"; filterSubject.next($ctrl.filter); // ... var filtered$ = stitched$.combineLatest(filter$, \_filter);

.do`ing things

Rxjs is meant to be largely a functional language. Nearly all the functions take in something and return a new something: map, filter, etc. While it is certainly possible to use your operator functions and handle creating side effects, it's not in the spirit of Rx. Instead, when you need to cause side effects such as muting a state object or the $ctrl itself, you should wrap that code in stream$.do() code. This makes it immediately obvious to the next developer (including you 6 months from now), where and what the side effects are.

It's great for handling these kind of scenarios:

  • Setting results into a cache
  • Logging what the stream$ value is before and after an operation. Fun note, you can use just: .do(console.debug)
  • Toggling loading states
var people$ = Rx.Observable.fromPromise(ApiService.getAllPeople()).do(function ( pilots ) { cache.pilots = pilots; });

Some RX gotchas

I haven't displayed any in this article, it's beyond the scope of an introduction. There were at least three notable confusing points along the implementation when I built the real code this was abstracted from.

Observable functions sometimes change this bindings

Pay close attention to the way your service methods are being called. A crufty old bit of services I worked against relied on this. .fromPromise didn't have this weakness but I ran up against at least one case where I needed to use something like ApiService.getAllPeople().bind(ApiService).

Hot vs Cold & Multicasting

Your observable is entirely capable of processing code in the background, with or without you listening. This is most often useful when real time data has to be ready for when it's required. Think keeping a socket open against a stock streaming service. I found this article Cold vs Hot Observables by thoughttram very useful initially to understand this. A better article once you can understand the previous would be one by the Rx project lead: Hot vs Cold Observables by Ben Lesh. Both of those articles are going to bring up the concept of multiple subscribers. It's a concept you're going to need, shortly into any project of some complexity. Just watch for the moment you run into stream$.something() causing a branch. If you don't make a multicast, you're actually publishing two Observables of stream$. The results can be unpleasant.

Documentation

For the love of everything you hold dear... check the documentation set you are using. A great deal of the links and search engine results on the net are for RxJS 4.x. This example, and Angular 2, both use RxJS 5. They not compatible documentations, even for the same function. Methods you'll use every day such as .distinctUntilChanged have had parameter order changed. Save yourself headache, and recheck, every time you look something up.

Getting started with RxJS

Ben Lesh has a lovely talk on YouTube that goes into many of the concepts you'll want to learn. While I recommend the entire talk (it's only 30 min), the most important bits to me were at 8:32. He talks specifically about how to start trying to use it, and the most common operators. There are a lot< of operators, but the general set he'll mention are a great super set. Have a watch: RxJS 5 Thinking Reactively | Ben Lesh