If you trying to build an Angular application in reactive style using RxJs, you will likely run into one of these common pitfall scenarios at least once.
In this post, we will go over some common trouble situations, explain why the situation occurs and how to fix it. If you have used RxJs before, then try to guess the problem in each scenario, just for fun!
Pitfall 1 - Nothing Happens
Let's say that we have finished writing a service method, where all the data will be saved to the backend with the click of a button:
We will plug in a messages component and an error handling service later to show any error messages that might occur, but right now we will simply trigger the save operation via a click handler:
And there we have it, the Save functionality is implemented. Except that when we try this out, try to guess what happens?
...
Nothing happened LOL :-) !
But the method is being called, or so we conclude after some debugging. So what's going on here?
Pitfall 1 Explanation and how to avoid it
An observable itself is just a definition of a way to handle a stream of values. We can think of it as something close to a function. Creating an observable is somewhat similar to declaring a function, the function itself is just a declaration. Calling the function is something entirely different, as defining a function does not execute its code.
We need to call the function in order for something to happen, and that is also the case with an Observable: we need to subscribe to it in order for it to work!
So in order to fix this issue, we can do the following at the level of the service:
Notice that now we are returning the Observable that we created, while before the Observable was created and the method returned without the Observable being subscribed to.
In the calling component, we can now add a subscription to this Observable:
Now that we have a subscription, the HTTP lesson creation POST call will be sent to the backend as expected, which solves the issue.
In order to avoid the issue we need to ensure that we subscribe to the Observables that are returned by the HTTP service, either directly or via the
async
pipe.
Sometimes however, the issue is not that an HTTP call is not being made, but by the contrary that too many calls are made!
Pitfall 2 - Multiple HTTP requests
Speaking of the async pipe, let's give it a try, and pass it in some observable that we get back from the service layer. The observable will emit a list of lessons:
But wait, you also want some error handling just in case the call fails:
So we add a subscription to handle the error, and pass in the observable to the async pipe. So what could have gone wrong here ?
Nothing, so we try this out and everything is working. But for whatever reason you decide to have a look at the network tab of the Chrome Dev Tools.
And what do you see ?
... /lessons GET 200 OK
... /lessons GET 200 OK
That's right, duplicate HTTP requests ! The request for reading the data is being issued twice! So what is going on here?
Pitfall 2 Explanation
It turns out that as the Observable is just a definition, let's remember that in a sense its something close to a function declaration, if we subscribe to it multiple times this means that each time a new HTTP request will be issued, one for each subscription.
Just like calling a function multiple times gives you multiple return values.
So if you subscribe multiple times to an an Observable returned by the Angular HTTP Client, that will result in multiple HTTP requests!
The problem this not what we where trying to implement, we want to issue only one HTTP request, emit the data and handle errors if they occur otherwise print the data to the screen. But what is happening instead is:
- one request gets back and the data is shown on the screen
- another request is sent to see if an error message will be displayed
With this scenario if the errors are intermittent it could even happen that the first request goes through and the second gives an error.
How to fix Pitfall 2
So how to fix this? This is one way to do it in this scenario:
The shareReplay
RxJs operator solves the problem of multiple unintended HTTP requests in an elegant way, and was added to the RxJs library with the specific case of HTTP requests in mind.
What this operator will do, is to ensure that only one HTTP request will be made to the backend in the first subscription, and then the result of that request will be served from memory to any other subsequent subscribers.
But no further HTTP requests will be made, as long as we keep subscribing to the same Observable that we received from the service initially.
This is usually the behavior that we expect from data modification observables that we get back from the service layer: we expect the data modification operation to be done only once, and the result can be shared with any view subscribers.
We wouldn't want the data modification HTTP request to be repeated multiple times for every view subscriber, as the data being saved is exactly the same and that would be redundant.
Its important to keep in mind this multiple subscription situation when building service layers with the HTTP module.
Consider the use of the shareReplay
operator as a practical solution for many of the Observables returned by an Angular HTTP-based service, as this will avoid many unintended HTTP requests in your application.
Another situation that happens not so much with the HTTP module but with libraries like AngularFire has to with Observable completion.
Pitfall 3 - Router gets Blocked?
Imagine that you are building a Router Data Resolver using AngularFire and not the Angular HTTP Library. This issue wouldn't happen with the HTTP library as we will see in a moment.
Also this router scenario is just an example of the underlying problem, that could manifest itself in other ways. The goal of the data resolver is to load some data before the routing transition completes, this way the data is already available when the new component gets rendered.
Let's have a look at some router configuration for a data resolver:
This configuration means that if we hit a url /edit
we will load a
EditLessonComponent
, but before we are going to load some data first: a list of lessons. For that we define a router data resolver:
Now that we have finished implementing our resolver, we try to trigger the router transition where the resolver is applied, and then try to guess what happens?
...
Again, nothing happened! The router transaction did not occur, we stay at the original screen and the UI does not get updated. Its like the transition was never triggered. What could have happened here?
Pitfall 3 Explanation
This has to do with both the way the router works and the way the AngularFire observable API works. The router in this case will take the observable that we returned and it will subscribe to it. Then it will wait for the data to arrive and for the Observable to complete, and only then the route transition is triggered.
This is because the router needs the data to be available before instantiating the target component.
After all, the main goal of the data resolver is to retrieve the data so that the data is ready available when the target component gets created.
So this means that if we return an observable in a data resolver, we need to ensure that not only it fetches the datan but also that it completes, so the router knows that the data is ready.
The router does not assume that the Observable will emit only one value, and it will wait for the Observable to to either complete or error out before it completes the routing transition.
But the question is, how can we ensure that our observable always completes? In this case we can for example simply call the first()
or take(1)
operators and create a derived observable that completes after the first value is emitted:
Always keep Observable completion in mind
With this the route navigation will complete successfully. This router scenario is just one possible manifestation of what can happen if we accidentally overlook Observable completion.
Completion is something important to happen in many situations that relate to Observables, take for example the RxJs concat
operator: If we pass it an Observable that never completes, the concatenation would not happen.
In the case of the Angular HTTPClient module, the returned observables always emit one value and then complete (unless they error out), so this is why the situation above would not happen using HTTP Client Observables.
But in the case above, because the AngularFire observables are meant to continuously emit the server pushed data changes of the realtime database, they don't complete after one value gets emitted.
This means that because the Observable will not complete, the router will "hang" the routing transition and the UI will not show the target navigation component.
The solution is to always make sure that the Observables returned by Data Resolvers complete properly, which already implicitly happens for HTTP Observables, but not for AngularFire Observables as Firebase Observables are long-lived.
Summary
These are some of the most common RxJs pitfalls that we can find when building Angular applications:
- forgetting to subscribe, and then nothing happens, like in the case of HTTP Observables
- subscribing too many times unintentionally, or not sharing execution results with subsequent subscribers: this can very easily happen with HTTP Observables
- forgetting to complete: this does not happen with HTTP observables as they either emit one value and complete or error out, but it is prone to happen with long-lived Observables
If we happen to find ourselves in a situation where something is not working as expected, asking these questions will help you find the solution:
- Has this observable been subscribed to?
- how many subscriptions does this observable have?
- when does this observable complete, does it complete?
- Do I need to unsubscribe from this Observable?
I hope you enjoyed the post, I invite you to have a look at the list below for other similar posts and resources on Angular.
I invite you to subscribe to our newsletter to get notified when more posts like this come out:
And if you would like to learn a lot more about RxJs, we recommend checking the RxJs In Practice Course course, where lots of useful patterns and operators are covered in much more detail.
If you are looking to use RxJs in the specific context of an Angular application, we recommend the Reactive Angular Course, where we cover lots of commonly used reactive design patterns for building Angular applications.
If you are just getting started learning Angular, have a look at the Angular for Beginners Course:
Other posts on Angular
If you enjoyed this post, have also a look also at other popular posts that you might find interesting:
- Angular Router - How To Build a Navigation Menu with Bootstrap 4 and Nested Routes
- Angular Router - Extended Guided Tour, Avoid Common Pitfalls
- Angular Components - The Fundamentals
- How to build Angular apps using Observable Data Services - Pitfalls to avoid
- Introduction to Angular Forms - Template Driven vs Model Driven
- Angular ngFor - Learn all Features including trackBy, why is it not only for Arrays ?
- Angular Universal In Practice - How to build SEO Friendly Single Page Apps with Angular
- How does Angular Change Detection Really Work ?
- Typescript 2 Type Definitions Crash Course - Types and Npm, how are they linked ? @types, Compiler Opt-In Types: When To Use Each and Why ?