One of the ways we can make the user experience a better one is to show them that things are happening while they wait. For instance, if they are waiting for a page a load, and it takes a bit to do so because there are multiple API calls and responses with data being formatted for display in a form, then it might be helpful to the user to see a page loading indicator instead of just nothing, leaving them with the impression that the app has crashed. Or worse yet, a page with empty spots which gradually have stuff appearing in them as data arrives and is told where to display itself in the layout. Few things scream “Unpolished!” like a view that loads in bits and pieces.
Fortunately, it is not difficult to solve this problem in Angular. With the introduction of Rxjs to the framework it suddenly became much easier to manage asynchronous events occurring behind the scenes by tapping into observables (which are worthy of their own series of posts I hope to get to in time).
Take a small app that searches for a movie by title and displays information about that movie on the page. When you type into the search input and press enter, it submits an API call to some server that returns the information we want about the movie in question. While we wait for this API to respond, depending on the quality of the Internet connection or the responsiveness of the API, we may want to give the user a heads up that the app is working on getting the information to them ASAP so they know it is still operating. In this case, it will be a progress spinner.
Let the great world spin for ever down the ringing grooves of change. -Alfred Lord Tennyson
The problem can be broken down into the following:
- We want the app to display the spinner while it makes the API call.
- Once the API returns with the data we want, our app will update the view with the data.
- The view should then remove the spinner, and display the information about the movie on the page.
- We should handle two possible kinds of errors: a) the search returned no results, and b) there was an HTTP error due to a failed request to the API. In both cases, the spinner should be hidden once the error condition is met.
Let’s look at a simple example that demonstrates one way to resolve this problem. A few things to note about this example:
- We’re going to be using the spinner component from Angular Material. There are plenty of alternatives and you can even create your own in CSS fairly easily, but since this post is primarily concerned with how to implmenent the timing of showing and hiding the spinner.
- I’m using the Open Movie Database for this example.
- The code for this example can be obtained here.
The part I’m going to focus on for this lesson is going to be the movie component (located in the movie folder under the app folder). The rest of the app is really just the scaffolding that allows the movie component to have a place to exist.
Let’s start with the HTML structure of our movie component.
We have a simple form with an input into which the search term is entered and a button to trigger the search (the input also triggers it by pressing enter). Below that is a div where our resulting movie information is shown. Below that is a div to display a message in the event of a failed search. Both of these previously mentioned elements have an ngIf directive stating that the showError state variable is determining which is shown, but more importantly for this example, it also depends upon the value of loading$ which is unpacked asynchronously by both components using the async directive.
So the spinner will be shown while loading$ is shown to be true, and hidden while shown to be false. Likewise, the movie details div will only appear when loading$ is false.
Lets move onto the Typescript portion of this component:
We can see that loading$ is initialized as a BehaviorSubject, with a value of false. This causes the spinner to be hidden at the start, which only makes sense since we want it to appear while we are making the API call for the movie info after a search value is submitted. When the search button is clicked, it triggers a call to the onSearchClicked function. The first thing that function does is set loading$ to push a value of true, which immediately triggers the spinner to appear. Then an HTTP request is made to the OMDB API to get the movie info with the title that matches the search input. If it fails to find anything, it triggers the message property to be set to a friendly message to give the user, but it it does return a movie for us, it sets the movie data up for the component to display, and then sets loading$ to push a value of false to the listeners, triggering the appropriate behavior, that is, the spinner drops out of view, and the movie details section appears. We are careful to have this change to loading$ inside of the HTTP GET request subscribe method call, since it is critical that the change to loading$ occurs after the API call has completed successfully.
Finally, in the event of an HTTP error response, we set loading$ to false as well, because otherwise the error message won’t be displayed and the spinner will just keep spinning forever, with our poor user in the dark about what happened.
The key to how the spinner knows when to be show itself and when to hide is in the loading$ property of the component, which is an instance of BehaviorSubject. What is a BehaviorSubject, you ask? To start with, it is a type of Observable, which if you have worked at with Angular you have probably used at some point, but an observable is essentially like a producer of data that decides when to push a new value to its consumers. The consumers of data could be just about anything but in this context it is the two divs in our movie.component.html file that we want to show or hide depending on whether the API call is done.
Why not just set a normal property variable, something like:
public loading = false;
Well, that would still work, but we would be turning over the actual updating of the state variable to Angular, and Angular can get pretty aggressive about how it does this. Instead of asynchronously updating the state variables as needed, we’d be forced to let Angular pull in the new value. That can work in a lot of cases but here we want the loading$ state to be determined asynchronously, and an observable lets us do that. This is a topic that warrants a much deeper look, and if you want to learn more about observables, take a look at the documentation for RxJS and the site Learn RxJS, both great resources for learning about tapping into the power of Observables.
What is specific about BehaviorSubject is that it allows casting its value to multiple observers, namely our two divs containing the movie details and spinner.
Just as a side note, I wrapped the API call and its callback in a setTimeout to force a delay. The thing is that the OMDB API responds so fast (good on them!) that without that setTimeout (or throttling your browser) you would barely see the spinner and I wanted to make clear that it works as it should. But what about a situation where you would prefer not to show the spinner right away but give the API time to respond, and only show the load indicator if it takes longer than a certain time? We’ll discuss that problem in the next article in this series.