AngularJS: centralized application loading status handling using http interceptors

It’s a common need in a JavaScript application (especially a single page app) to show a spinner and/or a message while data is getting retrieved from the server (that is while the ajax call is in progress). In fact an ex colleague asked me how I manage this in AngularJS just a couple of weeks ago and I want to share my implementation since I think that’s the cleanest and more effective way to achieve the goal using native framework’s features. Moreover it’s also useful to handle ajax errors in a centralized and generic way and I’m gonna show that too.
AngularJS has a very useful feature: HTTP Interceptors, these ones are services that get automatically called on each ajax request step (before an ajax call, after an ajax call and so on) once they are registered using the $httpProvider.
So they are the perfect place where to write our logic for generic error handling and loading state, in fact the official documentation says:

For purposes of global error handling, authentication, or any kind of synchronous or asynchronous pre-processing of request or postprocessing of responses, it is desirable to be able to intercept requests before they are handed to the server and responses before they are handed over to the application code that initiated these requests. The interceptors leverage the promise APIs to fulfill this need for both synchronous and asynchronous pre-processing.

So what I do in my app is to create the following interceptor:

angular.module('myapp').
    service('LoadingInterceptor', 
    ['$q', '$rootScope', '$log', 
    function($q, $rootScope, $log) {
        'use strict';

        return {
            request: function(config) {
                $rootScope.loading = true;
                return config;
            },
            requestError: function(rejection) {
                $rootScope.loading = false;
                $log.error('Request error:', rejection);
                return $q.reject(rejection);
            },
            response: function(response) {
                $rootScope.loading = false;
                return response;
            },
            responseError: function(rejection) {
                $rootScope.loading = false;
                $log.error('Response error:', rejection);
                return $q.reject(rejection);
            }
        };
    }]);

It makes use of $rootScope to store the loading state “globally”, which is set to true as soon the request is created (request method) and set to false when the request is invalid (requestError) or the call has been completed (response or responseError).
It also logs errors in case of requestError and responseError.

Then I register the interceptor in the module config() block:

.config(['$httpProvider', function($httpProvider) {
    $httpProvider.interceptors.push('LoadingInterceptor');
}]);

As you can see it’s registered as a simple plain string rather than using the usual dependency injection (AngularJS knows how to properly load the service).

Regarding the display of a message/spinner to the user, in my templates all I have to do is something like:

<div data-ng-if="loading">
    Loading...
</div>

That’s really cool! clean! simple! effective! :)

UPDATE:

I realized that my interceptor doesn’t work properly when concurrent xhr requests are involved.
For example, if a request A starts and then a request B starts, if B completes its job before A, the loading status is defined as “false“. To fix this issue I modified it by tracking the count of initialized requests and completed ones, so the final reliable implementation of the interceptor is the following:

angular.module('crs').service('LoadingInterceptor', ['$q', '$rootScope', '$log', 
function ($q, $rootScope, $log) {
    'use strict';

    var xhrCreations = 0;
    var xhrResolutions = 0;

    function isLoading() {
        return xhrResolutions < xhrCreations;
    }

    function updateStatus() {
        $rootScope.loading = isLoading();
    }

    return {
        request: function (config) {
            xhrCreations++;
            updateStatus();
            return config;
        },
        requestError: function (rejection) {
            xhrResolutions++;
            updateStatus();
            $log.error('Request error:', rejection);
            return $q.reject(rejection);
        },
        response: function (response) {
            xhrResolutions++;
            updateStatus();
            return response;
        },
        responseError: function (rejection) {
            xhrResolutions++;
            updateStatus();
            $log.error('Response error:', rejection);
            return $q.reject(rejection);
        }
    };
}]);

How to make AngularJS and Django play nice together

In order to make AngularJS working as I wish in my Django app, these are the settings that I’ve adopted:

1. Differentiate Angular templates symbols from Django ones:

Both Angular than Django use doble curly braces to mark variables and/or expressions ({{ myVar }}).
In order to have the full control on how and by who our templates are rendered, I redefined the Angular interpolations signs in the config() method of my client app.

$interpolateProvider.startSymbol('{$');
$interpolateProvider.endSymbol('$}');

2. Change the default Angular Content-type header used in POST method:

Angular defines the “Content-Type” header as “application/json” for ajax POST, but Django doesn’t understand that content properly and as result, the POST data is not an object as we expect but rather a string! So, I modified the default content type as “application/x-www-form-urlencoded” (which is the format used by jQuery and other JavaScript libraries).

$httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

3. Assign the CSFR token to each ajax call automagically

In Django templates we can use the tag {% csrf_token %} inside a form to print an hidden input containing the token. But when it comes to making an xhr post request, how can we pass the token in an effective and DRY manner? The answer I gave myself is to set a default http header for ajax calls containing the value of the token obtained by reading the session cookie (in this way this stuff is handle 100% by JavaScript).

$http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken;

Differently from points 1 and 2, this is done in the run() method, since $cookies is an Angular service and can’t be used in config() block (in that function only provider objects can be used).
In order to use $cookies we have also to import “angular-cookies.js” in addition to the base “angular.js“.

The final configuration is the following:

angular.module('myapp', ['ngCookies']).
    config([
    '$httpProvider', 
    '$interpolateProvider', 
    function($httpProvider, $interpolateProvider) {
        $interpolateProvider.startSymbol('{$');
        $interpolateProvider.endSymbol('$}');
        $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
    }]).
    run([
    '$http', 
    '$cookies', 
    function($http, $cookies) {
        $http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken;
    }]);

UPDATE:

Starting from Angular 1.2, you have also to set default headers in order to use Django helper method is_ajax() in your views:

$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';