Mastering RxJS: operators and functions that can bite you when you don’t expect

Alexander Poshtaruk
Angular In Depth
Published in
9 min readJul 18, 2019

The things you may not pay attention to but they are good to know

Photo by Lucas Vasques on Unsplash

AngularInDepth is moving away from Medium. This article, its updates and more recent articles are hosted on the new platform inDepth.dev

Prerequisites: You should be familiar with RxJS and have some practical experience in using it.

#1. fromFetch function vs ajax const

RxJS lib has two ways to wrap network request in Observable. And They have difference:

fromFetch function — uses the Fetch API to make an HTTP request.

snippet link

2. ajax function — use XhrHttpRequest under the hood

snippet link

Both return Observable so we expect unfinished network request should be canceled on unsubscribe. Usually it works like that but there is small remark in the official documentation for fromFetch:

WARNING Parts of the fetch API are still experimental. AbortController is required for this implementation to work and use cancellation appropriately.

Will automatically set up an internal AbortController in order to teardown the internal fetch when the subscription tears down.

So beware if you use it in IE11 browser since AbortController is not supported there.

*Remark from Nicholas Jamieson: there is a problem with the current implementation of `fromFetch`. See this issue.

#2. forkJoin vs zip

There is a nice tweet of 🦊 Reactive Fox 🚀 “If you know Promise you already know RxJS”:

zip is used instead of forkJoin

So how it works:

  1. We create many Observables that will do HTTP requests with fromFetch function.
  2. zip function gets an array of such Observables and subscribes them causing HTTP requests to be performed.
zip marble diagram (taken from https://rxmarbles.com/#zip)

3. Zip waits until every argument Observable emit values with same index (index = 1 (or 0:) in our case) and emits an array of values .

4. Since all argument Observables produce only one value — so after all responses are fetched — zip produce array of responses.

You may think — everything is OK. And actually it is:) But only in this particular case. Why? because If you try to feed zip function with Observables that produce more then one value — you will get unexpected behavior (more then one emission or never-completed result Observable).

To prevent such possible drawbacks you can use forkJoin function.

forkJoin waits for all argument Observables to complete and then emit an array of last emitted values (to compare: zip emits an array of values with same emission index).

forkJoin marble diagram

Now our example will look like this:

snippet link

Now you are armed💪.

#3. Using materialize, dematerialize to mock delayed erred Observable

Say you have such function that makes a network request in Angular:

snippet link

Now to mock HttpClient successful response and emulate network latency you can provide such value:

snippet link

And you may think that to mock delayed erred response it is enough to do like this:

snippet link

But it will not work the way you expect. Once throwError produce error value — delay will be ignored and your error handler will be run immediately.

How to fix that? How to prevent error event to omit delay operator?

We can convert error emission to internal RxJS notification object and then return it back to a usual error with materialize and dematerialize operators.

Now our mock will look like this:

snippet link

You can read more about these operators in the official documentation here and here.

#4 Using timer function with one argument instead of of(0).pipe(delay(x))

Previously If I had a need to emit once with initial delay i used such expression:

snippe link

It works well, but can we do it even better? Yes, with RxJS timer function. Timer definition looks like this:

timer(dueTime: number, period, scheduler)

duetime — initial delay before starting emissions

periodThe period of time between emissions of the subsequent numbers.

But what if I need to emit only once? Just use timer with one dueTime argument:

snippe link

Do you like the article? You can find more interesting RxJS staff in my video-course “Hands-on RxJS”. It may be interesting as for beginners (Section 1–3) as well as experiences RxJS developers (Sections 4–7). Buy it, watch it and leave comments and evaluations!

#5 takeLast with no param returns undefined

Once upon a time, I stepped on a rake by using takeLast() operator (RxJS ver 6.5.x). Just to remind you:

takeLast(count: number)
Waits until source is complete and then emits only the last count values emitted by the source Observable.

Somehow I expected that if applied without any param it returns 1 last value by default:

snippet link

But no — it returns undefined. To return 1 last value you should use it like this: takeLast(1):

snippet link

You can check this behavior in this codepen. Beware!

What can we do? Just open an issue and create a pull-request with fix🙃

#6 from(fetch(url)) — eager vs defer(()=>from(fetch(url)) — lazy.

This tip came from another tweet of Juan Herrera.

OK, now let's go through it with explanations.

You know that Observables are lazy(do nothing until subscribed), Promises are eager (do everything when created) (more here).

Also, fetch returns Promise. This means that if we call fetch(‘url’) — network call will be performed at once. We can convert Promise to Observable with RxJS from the function but since fetch is called first — then anyway it will work in an eager way.

from(fetch(‘url’)) // will work in eager way

So how to fix it? We can use RxJS defer function.

defer(() => fetch(url)) //will work in lazy way

One more note — in RxJS we already have special function that implements lazy fetch method — fromFetch.

defer(() => fetch(url)) //will work in lazy way
fromFetch(url) //do the same

If you take a look at fromFetch sources — you may observable how it implements deferred behavior.

Small hint 😎:

Remember? We reviewed fromFetch in tip #1.

#7 of() == EMPTY

Typical switchMap operator example with the condition may look like:

Make a network request for odd indexes only (snippet link)

It works this way:

  • interval function generates incrementing numbers with some delay.
  • switchMap callback checks if the emitted number is odd, if yes — it makes request wrapped in Observable and subscriber gets a result. If no — we return empty sequence constant — EMPTY (it completes at once).

Nothing special here. Recently I found out that we can us of() with no params to reach the same result. Now our example will look like:

of() returns empty sequence as well (snippet link)

You can check yourself and play with it here.

#8 toPromise gives you the last value when Observable completes. Be careful when you use it with Subjects.

Angular2 subreddit sometimes brings interesting knowledge as well :-)

I will comment the code from this help request:

  1. You have WarehouseService, that provides some value with RxJS BehaviorSubject. As usual, we have some method getDefaultWarehouse there to get this Subject as Observable:

2. Now we want to subscribe to emitted value and then use it with JS await. So we need to convert Observable to Promise with toPromise() method.

It will look like this:

So when DB is ready we emit row.defaultWarehouse value (see previous snippet) and in index.ts we await to get defWarehouse value.

But it never resolves. WHAT?

The reason is simple: RxJs method ‘toPromise()’ waits for your observable to complete. Since we use BehaviorSubject, that just emit value but don’t complete — toPromise will never be called.

You can solve it in two ways:

  1. If WarehouseService should emit value with BevahiorSubject only once — then just complete it after emission:

2. Or just subscribe to Observable to run a subsequent action.

snippet link

You can play with the code here.

More to read:

  1. RxJs operator ‘toPromise’ waits for your observable to complete!

#9 Different ways RxJS Subjects works after completion (Behavior, Replay, Async)

Recent Angular-in-Depth 2019 conference in Kyiv, Ukraine remind me about different behavior of RxJS BehaviorSubject, ReplaySubject and AsyncSubject after completion. Not to be very verbose — I just created a comparison table:

You can play with it here.

BehaviorSubject doesn’t return the last value after completion.

More to read and watch:

  1. Read Wojciech Trawiński’s article “BehaviorSubject vs ReplaySubject(1) -beware of edge cases”.
  2. Nice talk of Michael Hladky: “A deep dive into RxJS subjects” at AiD conference.
  3. Understanding RxJS BehaviorSubject, ReplaySubject and AsyncSubject”.
  4. All Angular-in-Depth conference 2019 talks videos.

#10 If we reassign Observable used in Angular template with asyncPipe — it will continue working.

Usually, Angular asyncPipe usage looks something similar to this example:

snippet link

And we know that asyncPipe will handle all the Observable subscribe/unsubscribe activity to prevent memory leaks.

But what if we just re-assign this.name Observable with another Observable instance? Will AsyncPipe do unsubscription? Will it re-subscribe to a new Observable instance? Let's check it out!

  1. I will add a button to re-assign this.name with new Observable instance.
snippet link

2. Also to check if AsyncPipe will unsubscribe I will create a copy of standard Angular AsyncPipe from github and put it to our demo Stackblitz project as custom Pipe.

And added console.log to unsubscribe method to observe whether previous observable is unsubscribed.

Now, let's check what happens when Observable property is re-assigned.

AS you can see — previous Observable is unsubscribed. Phew..we can sleep peacefully now!

You can check results in this Stackblitz playground.

Conclusion

So, seems like you’ve become 10 gotcha’s richer 💰.

If you like this article — tweet about it 🤓

Let’s keep in touch on Twitter.

Have your own tricky RxJS cases solved? Share in comments!

Special thanks to Nicholas Jamieson, Lars Gyrup Brink Nielsen, Alex Okrushko for reviewing this article!

--

--

Alexander Poshtaruk
Angular In Depth

Senior Front-End dev in Tonic, 'Hands-on RxJS for web development' video-course author — https://bit.ly/2AzDgQC, codementor.io JS, Angular and RxJS mentor