Use cases for the Aurelia HttpClient

One of Aurelia’s strengths is the way that it provides a set of well-designed building blocks that you can use in a variety of different ways. One of these building blocks is the HttpClient, a wrapper for the browsers Fetch API which is a modern replacement for the old XMLHttpRequest object.

This morning I was looking for a way to show loading progress in a simple and consistent way across a new application I’m building. The goal was to give the user some kind of visual feedback any time I was making an Http request. Looking around I found the nprogress library, which does a great job of showing a slim progress bar in a way similar to apps like Youtube.

Hooking this up was simple:

npm install nprogress --save

Import the styles at the top of my app.ts:

import 'nprogress/nprogress.css';

Then, in my application, I set the HttpClient up with an interceptor to automatically add some required headers to each request. Interceptors are one of those powerful features that buy you a great amount of flexibility when using the HttpClient. So, I decided to modify my interceptor slightly to also report progress via nprogress.

First I needed to import the module:

import * as nprogress from 'nprogress';

From there it was a simple matter of modifying my intercepter to report progress on the start and end of each request:

    get interceptor(){
        return {
            request(request) {
                nprogress.start() //report loading started
                request.headers.append('appId', this.state.appId);
                return request; 
            },
            response(response) {
              nprogress.done() //report loading finished
              return response;
            }
        };
    }

This took me only a few minutes to wire up, but is working nicely. It got me thinking, there are probably many other novel use cases for the various features of Aurelia’s Fetch client and HttpClient, interceptors, and more. I’d be interested to see examples of what other’s are doing.

Edit:

As @huochunpeng quite rightly points out, there is another feature of the HttpClient which makes this even simpler. The HttpClient has an isRequesting property to maintain the state of when requests are being made. So, a more elegant way to implement this logic is to create a custom element, like the loading indicator from the app contacts sample https://github.com/aurelia/app-contacts/blob/master/src/resources/elements/loading-indicator.js.

import * as nprogress from 'nprogress';
import {bindable, noView} from 'aurelia-framework';

@noView()
export class LoadingIndicator {
  @bindable loading = false;

  loadingChanged(newValue){
    if(newValue){
      nprogress.start();
    }else{
      nprogress.done();
    }
  }
}

This element is then included in the top of your page, and can be used to trigger nprogress as follows:

  <loading-indicator loading.bind="router.isNavigating || http.isRequesting"></loading-indicator>
5 Likes

Great work Sean - just something that you might like to consider:
#1 if there is more than one concurrent request, then the first response will mark the progress as finished, even though others are still waiting. You could perhaps increment a counter on each request, then decrement on each response, and set the progress as done only when the counter is 0.
#2 do you happen to know if the response callback occurs when there is a timeout on the HttpClient?

1 Like

Thanks @Stuart, that’s a great point actually. I should have thought about subsequent requests. Will play with this a bit today to get it working in different scenarios and update the sample. Good question on the response callback. I’ll do some testing on this and update the thread :smile:

It’s actually in contact manager tutorial.

https://aurelia.io/docs/tutorials/creating-a-contact-manager#adding-a-loading-indicator

Using binding is easier than your manual approach.

<loading-indicator loading.bind="router.isNavigating || api.isRequesting"></loading-indicator>

Furthermore, using binding you can do trick like this Tip: show spinner only when something is slow

<loading-indicator loading.bind="router.isNavigating || api.isRequesting & debounce"></loading-indicator>
3 Likes

Another good use case for the interceptor is to create stubs for Rest endpoints which arent yet available. E.g your Backend Team hasnt yet released a new feature but you’d still like to finish the Frontend part. Once the new routes are available simple disable the interceptor and rock on

2 Likes

That’s a nice point as well. Good that it hooks in for both the requesting and navigating cases. The thing I appreciate the most about the contact-manager manager approach of using a requesting variable is that the reporter of this state doesn’t need to know about nprogress. On the flip side, the benefit I see of attaching this logic to the Http interceptor instead of inlining it in the service class calls is that you only need to specify it once. In the contact manager, each web API method needs to explicitly specify whether the app is making a request.

From the tutorial:

 getContactDetails(id){
    this.isRequesting = true; 
    return new Promise(resolve => {
     ...
  }

  saveContact(contact){
    this.isRequesting = true;
    ...
}

So even though binding is convenient in some ways, we still have a manual approach for reporting that we’re requesting.

Perhaps a hybrid approach would work best, where the app set a requesting state for the app when there the request count was greater than or equal to 1.

That’s a great suggestion actually, typically we just stub these out on the asp.net core web api side, but this approach would obviate the need for that entirely.

You don’t need to manually set isRequesting, it’s a observable property on aurelia-http-client itself, same as the observable property isNavigating on the router itself.

https://aurelia.io/docs/api/fetch-client/class/HttpClient/property/isRequesting

2 Likes

Ah very nice, that certainly simplifies things! So in the contact manager tutorial, we only manually set the requesting state because we’re not actually using the HttpClient to make the request (it’s just stubbed out using Promise.Resolve for demo purposes.

Which line in the tutorial are you talking about?

The contact manager tutorial https://github.com/aurelia/app-contacts. What I mean is that the requests are faked at the back end rather than using the HttpClient https://github.com/aurelia/app-contacts/blob/master/src/web-api.js.

ok, got it, because it’s a fake in-memory api, trying to mimic http-client. When you use real aurelia-http-client, you don’t need that.

1 Like

That’s cool. Never realized that aurelia-fetch-client had the isRequesting flag.

We use aurelia-fetch-client extensively to add special headers for Auth purposes as well as to return a more User friendly error messages when there are errors instead of returning what the backend returns which may be more developer-friendly rather than user-friendly.

We also reject calls before we even make the request if there are things missing, e.g. POST requests with no body. Or if the user is not currently logged in yet, we reject it unless it is a public endpoint. This saves a lot of requests to the backend which we know will be rejected anyways.

1 Like

Nice, I particularly like the ideas for intercepting the error responses to return clean error messages, and optimising HTTP traffic by avoiding unnecessary calls to private APIs. I actually never thought of using it that way but it makes a lot of sense. :+1:

@justindra for a layer between backend endpoints and frontend, there is service worker API specifically designed for this kind of work. You may want to investigate that, it got lots to offer, but doesn’t support IE.

2 Likes

@huochunpeng literally the only reason we haven’t used that is the need to support IE. But once we can drop that support for IE definitely will use it.

1 Like

I didn’t know about the isRequesting observable and started to use it now with great joy. Very helpful to provide user feedback on requests in progress.

I’m facing a situation which I don’t know if it’s a bug or else. When I use the fetch-client to make a request towards an API that is not reachable, the fetch-client rejects the call with a “Failed to fetch” error but the isRequesting property stays true. Is this normal behavior?

I’ve opened an issue on fetch-client repo is that can be of any help:

1 Like

Hi Ben,

i think it was an issue with fetch-client: https://github.com/aurelia/fetch-client/issues/119
v1.8 fixed it.

Best

1 Like