This lesson is for PRO members.

Unlock this lesson NOW!
Already subscribed? sign in

Reactive Programming - Using cached network data with RxJS

7:44 RxJS lesson by

In this lesson, we learn how to build the final feature of our user interface: how to use the cached network response to replace a suggested user when the user clicks on the 'x' close button for that user.

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

In this lesson, we learn how to build the final feature of our user interface: how to use the cached network response to replace a suggested user when the user clicks on the 'x' close button for that user.

Avatar
Noah

I noticed that when you testdrove the end result at the end of the video, you got a repeated user (andreymaxim, I think...). I guess it would be nicer to avoid that, and I guess also to avoid that 'x' gives a user that is already there.

In reply to egghead.io
Avatar
Andre

Noah, good remark! Our logic is simple here because we replace random with random, and there is possibility of collision with the past user. It would be nicer to avoid, and it is possible with RxJS, just a bit out of scope for this series.

In reply to Noah
Avatar
Przemysław

Is it possible to siplyfy the example by

var requestOnRefreshStream = refreshClickStream
.map(ev => {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
});

var requestStream = requestOnRefreshStream.startWith('https://api.github.com/users');

Or
var requestStream = refreshClickStream.startWith('click')
.map(ev => {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
});

Is this correct ?

Avatar
Andre

Correct, there are multiple ways of achieving the same behavior. The code presented in the video is the easiest for beginners to follow what is happening.

In reply to Przemysław
Avatar
Przemysław

Why you didn't model stream to :
responseStream.flatMap(function(listUsers) {
return Rx.Observable.from(listUsers);
}).subscribe(function(user) {
console.log("user ->"+user.login);});

  • We get a stream of users entities which can be consumed by subscribers.
Avatar
Oren

Hi Andre,
Can I merge more than 2 Observables?
Thanks

Avatar
Andre

Hi Oren, yes you can: Rx.Observable.merge(a, b, c)

In reply to Oren
Avatar
Sequoia McDowell

I like the series but it raises a ton of unanswered questions. For me, primarily,

  1. What's up with all the repetition/copypasta & is there a RX idiomatic way to remove that repetition?
  2. Each stream doesn't know about the others' state (yay!) so they can each load the same user (boo!). Glossing over this (how do I handle streams that need to be aware of the state of other streams or what they did?) seems like skipping a crucial question-- personally I need to understand this before I could consider using RX.
  3. Is it considered no big deal that some streams reach way back & access their upstream-streams in different ways? I.e. the "update suggested user" stream that is a grand-child (if you'll allow the expression) of the refresh click stream, but also directly consumes that stream to emit nulls. Marble diagram now needs to be 3D: needs arrows that go OVER other streams & skip 2 or 3 streams down. Is this kosher in the RX world?

Thanks!

Avatar
Rafael

Got this working on v5 using switchMapTo operator instead of the switchLatestFrom.

Avatar
Shailesh

@andrestaltz refreshclick.map(ev => null) executed in createSuggestionsStream and refreshClickStream.map(...//which returns URL to get the user) are passed the same event every time refresh is clicked correct? So they are two different streams consuming same "click"? what guarantees that null is always returned first before the actual result? the async nature of the fetching user? what if we are returning the user from cached array? Will still null be returned first?

Avatar
Andre

There are different ways of accomplishing this, and better ways where we wouldn't have race conditions. In this case, because it is an introduction only, I went for the simplest option where we rely on the fact that user fetching will be asynchronous and click reactions are synchronous.

In reply to Shailesh
Avatar
Shailesh

ok thanks Andre. can you please give me pointers for further reading regarding the race condition avoiding operators and ways. may be resources which explain such gotchas and possible ways. Ofcourse I will be continuously following all the courses here and elsewhere but getting this from "horse's mouth" will be best way to go about this.

In reply to Andre
Avatar
Andre

If you haven't yet, Watch the other courses on RxJS here in Egghead. I also plan to release the course about flatMap and similar operators, which are useful for many kinds of chaining and dependencies among observables. These are ways of handling race conditions as well.

In reply to Shailesh

There's one last feature that we haven't implemented yet, and that would be the small X button near each user. When this X is clicked, we're supposed to replace the user with another one. I already made it easy for us by selecting from the DOM each of these three X buttons and creating their respective event streams for clicks.

The feature is supposed to work in a similar way that refresh does, except it's supposed to refresh one user. That's the idea.

Let's take a look at how refresh was done. It was here in this createSuggestionStream function. This will return to us this event stream of the suggested user data for one user. That's why we have three of them, because we have three users.

How does this whole thing work? Let's recap it using marble diagrams. We have refreshClickStream, which is an event stream over time, and we also have requestStream. Which, by the way, this represents both of these different types of requests. We can even separate that into a dedicated event stream, just a minor refactor.

This represents either start up requests -- that's what merge accomplishes -- or request on refresh. We also have the responseStream. We have the suggestionOneStream, which is the output of our function here.

How does it work? Well, initially we start with the start up request, which is R. It happens in the beginning. Then after a short while we get the response for that request.

If we click the general refresh button, this event will be mapped to request here from the requestOnRefresh to an R. Then after a while, the response for that refresh will arrive.

How does suggestionOneStream use those event streams? Well, it maps each of these response stream arrays. This is emitting this big R. It means an array full of users. We map that to one random user. That's what we get here. We map this one to a random user, and we map that one to a random user.

Then we prepend this observable with a null, here. We're just saying that we start with a null user data. Then we merge with the refreshClickStream map to null. What does that mean? We get this refresh event. We map it to a null. Then we get an observable that looks like this, so we map that to a null. Then we merge this thing into the output here. Then we get that.

OK. Let's try to read this as the output to our function. Initially we have empty user data for the first user. That's correct. Then after a short while, we get the random user on start up.

Whenever we refresh, we should immediately empty that user data. Then after a while, that random user from the response will arrive.

How do we include the closing with the X button here? Let's write it here together with the others, closeClickStream. Since we have three different buttons, we have three different event streams for clicks. We need to use them in order to produce the respective user data.

It means we are only using clicks from the first button for the first user. The second button's clicks will be used for the second user's data, and so forth.

Now that we have this here, we can include it in the function arguments. Now, we can do something with it.

What are we supposed to do? Let's imagine after a while, here we go and we're going to press X on that button. What are we supposed to do? We're supposed to simply replace the user with a new user immediately here. We're looking for this U here.

We want to map this closeClickStream to a random user. How do we do that? Well, we can just get closeClickStream, and map it -- it's an event -- to a random user. We can call getRandomUser, giving it the list of users.

But, here we have a problem, right? We don't have a list of users available in the scope. Why? Because it's sort of trapped inside of the responseStream. I mean, the responseStream emits these Rs. These Rs are exactly what I mean with this list of users. So, this is the same as saying list of users.

This is the list of users, and it's sort of trapped inside the response stream. We want to get that. How do we do that? Well, what if we had an operator that would be called, "Map, But Please Also Use The Latest Value From That Stream"? You give that here, which would be in our case the responseStream.

Now, that's a really long name for an operator. People have made a nice name for this, which is called withLatestFrom. You can specify the sort of sister stream, the responseStream. And then we can get that value here.

This here will return us the observable that has...we can even try it. Whenever an X happens here, we're going to use the latest value from the responseStream. That's why it's called withLatestFrom. We're going to join this R and this X. I can actually name this X and R. And we're going to create a random user here.

That's what we're going to use to merge this into here, and then we finally get that U that we wanted. Cool. We just need to merge this guy into our output, like that.

OK, let's see. We can refresh everything, and we can replace one user.

HEY, QUICK QUESTION!
Joel's Head
Why are we asking?