- Use the RemoteData data structure from the @ngspot/remote-data library to describe data being requested from an API.
- Use RxJS and a custom operator trackRemoteData from the @ngspot/remote-data-rx library for best results.
Have you ever written a component or a service where an HTTP call is made to request some data from an API and display it to the user? That’s mostly a rhetorical question — most applications do that. There are common scenarios that need to be addressed when dealing with remote data.
Many times, I’ve written components and services where HTTP calls are made to request some data. Previously, my approach used an imperative style of coding (before I learned the magical power of data streams).
Although there are many issues with the code above (like manually subscribing, not using OnPush change detection, not using trackBy for the ngFor loop, a bug due to a potential racing condition, etc.), the code above works. Please ignore the imperfections for the time being. Most of them will be taken care of by the end of the article and the rest are omitted for simplicity.
Next, I realized that the call to the API takes time and I need to display some loading template while the data is loading. So I added theisLoading property to keep track of that!
Simple enough! But wait, what if the API returns an error? I want to display something to the user in this circumstance. I know how to handle this! So I introduced another property: error!
Ufff, three properties to keep track of all the possible state options and a whole bunch of code to maintain these three properties?! And that’s just for one API call. What if there are multiple API calls?? What I have does not have all possible states either. There’s one more — a case when data has not been requested yet.
In the example above, data gets loaded automatically when the component initializes, but that could be different. What if you want to display a prompt to the user with some instructions for the case when data has not yet been requested? That is a lot of spaghetti code!
The spaghetti code of handling all the possible states can be solved with a data structure that encapsulates all these possible cases:
You can get better type safety if you create a dedicated type for each of the states and then use a TypeScript union feature.
Now, I will create a few builder functions that return the RemoteData for each possible state of the request (1) not asked, (2) loading, (3) success, and (4) error.
With all of this in place, here is the rewritten component:
This is much cleaner! Only one property to maintain and this property handles all use-cases. The @ngspot/remote-data library has essentially been rebuilt. Feel free to use it!
But I can do better! Read on.
Remember the numerous issues mentioned at the beginning of the article?
Among them is a bug related to a racing condition. If a user presses the button “Load products” many times quickly, then many requests will be fired up. The chances are, that due to the timing of the network, the responses for these requests will be returned out of order. The response for the request associated with the very first click could end up coming back last. This means that the UI might not display the freshest data.
RxJS is perfect for handling asynchronous data streams. It has mechanisms for dealing with this sort of situation. In addition, it makes it easy to use OnPush change detection, which improves the performance of your application and can improve the general quality of your components.
Without further ado, here is the rewritten component using reactive streams and the RemoteData data structure.
This solution is much more robust. There are no manual subscriptions. Data is propagated to the template via reactive streams with theasync pipe, which allows the use of OnPush change detection. Finally, race conditions are handled via the switchMap operator, which automatically cancels any previous requests in flight and starts a new one.
RxJS allows building a custom operator using multiple existing operators. That’s what I had done with the example above — I took the operators used to handle the RemoteData loading, success, and error cases and extracted these operators into a custom operator called trackRemoteData. Find the trackRemoteDataoperator under @ngspot/remote-data-rx library. There are a couple more bells and whistles built-in.
With that, the code becomes even simpler.
There are similar solutions for handling remote data out there. I have tried most of them, but none provided me with the exact feature-set I wanted. Here are a few of them:
- Where it all began: “How Elm Slays a UI Antipattern”
Nonetheless, these solutions inspired me to create the two libraries that I now use in most of my projects. I hope you find them helpful too.
I wish you happy programming!
👏 Special thanks to Ana Boca for reviewing this article.