An example converting angular 1.5 to use RxJS
Published:
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 & 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