An example converting angular 1.5 to use RxJS

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:

  1. Upgrade to 1.5.x
  2. 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.
  3. Start rewriting as much $scope style work as you can into $ctrl components.
  4. 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.

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:

See the Pen Angular example - Pull paged api, stitch, filter, sort, and display data by Lance Gliser (@lancegliser) on CodePen.

Then the RxJS solution:

See the Pen Angular & RxJS example - Pull paged api, stitch, filter, sort, and display data by Lance Gliser (@lancegliser) on CodePen.

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.

  1. var filterSubject = new Rx.BehaviorSubject();
  2. var filter$ = filterSubject.debounceTime(150);
  3. // ...
  4. $ctrl.filter = 'skywalker';
  5. filterSubject.next($ctrl.filter);
  6. // ...
  7. 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

  1.  var people$ = Rx.Observable
  2.   .fromPromise( ApiService.getAllPeople() )
  3.   .do(function(pilots){ cache.pilots = pilots; });

Some RX gotchas

I haven't displayed any in this in this article, it's beyond the scope of an introduction. But 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 everyday 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

Comments

I'm tentatively looking at doing something similar - integrate rxjs with AngularJS 1.5, so your post caught my attention (thanks!).

The codepen example you provide uses rxjs **5** with AngularJS.

Now, I always thought that to integrate the two technologies, you would need to use the rx.angular.js bindings (https://github.com/Reactive-Extensions/rx.angular.js). This in turn means using rxjs 4 and NOT rxjs 5.

Yet your code example suggests otherwise - that you can use rxjs 5 with AngularJS 1.5.

If this is indeed the case then I'm going to dive in myself :-)

Questions

1. How far are you down the road of getting rxjs 5 and AngularJS working together?
2. Have you run into situations where you have to call $rootScope.$apply in order for angular to be aware that your subscriptions have changed the state of the application
3. Or perhaps another way of phrasing the second question - what are you having to do yourself that rx.angular.js bindings would be doing for you?

Thanks
Christian

Hi Christian,

Thanks for writing. Happy to answer.

1. 100% functional. The project this was abstracted from used rxjs at the service layer, with small things like this tied to the view layer.
2 + 3. Most of the work you're seeing me do is handled outside of Angular. You're correct on that. In order to alert Angular that it should start doing a scope digest we use $timeout(func). This allows 90%+ of the work to happen without the overhead of digest, and avoid multiple loops they might cause before our data fully prepared.

A note on $rootScope.$apply vs $timeout:
Either of this works to alert Angular and fire a digest. There's a weakness in $apply though. It's possible if two fire at once, or while a digest is already progressing the system will error. $timeout() gets around this by creating a queue.

Hi Lance,

Thanks for getting back to me - very helpful.

Knowing that someone else is getting these two libraries/frameworks to work together is quite encouraging I must say. I think I'll give it a go myself!!

Regarding the $apply vs $timeout

I've used a "safe" $apply in the past - ie call $apply only if there is no digest loop in progress.

The problem I see with $timeout is that it will be worse performance compared to a *single* digest loop. So my intuition says to use $apply and ensure that this is guarded to only be called once.

I'll experiment I think :-)

You're absolutely on base about the performance of $timeout vs $apply. Let me at least caution you on one thing. Remember, the primary goal in large projects is to best handle complexity. While $apply has performance gains, it is a somewhat risky choice. As the system grows, more things in abstract components and services may eventually cause digest loops when you're not expecting it. This situation is actually a bit worse because it's potentially harder to reproduce. It's subject to race and user action conditions. Just a little reminder to lean to the side of resilience over performance for anything outside the hot loop.

Add new comment

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.