Angular Request Interceptors

Home / Angular Request Interceptors

Request interceptors in Angular are extremely handy.  They are essentially like jQuery’s global settings for $.ajax requests.  There are some not-so-subtle differences and framework tie-ins as well.

One of the simplest things I like to use request interceptors is for displaying an activity indicator.  My favorite activity indicator is spin.js as well.

The basic interceptor has a few standards objects/functions that Angular will call. They are request, requestError, response, and responseError. A basic interceptor template might look like this:

(function () {
    var requestInterceptor = function ($q, $rootScope, $injector) {
        $rootScope.http = null;
        return {
            'request': function (config) {
                return config || $q.when(config);
            },

            'requestError': function (rejection) {
                return $q.reject(rejection);
            },

            'response': function (response) {
                return $q.when(response);
            },

            'responseError': function (rejection) {
                return $q.reject(rejection);
            }
        };
    };

    requestInterceptor.$inject = ['$q', '$rootScope', '$injector'];
    angular.module('myApp.infrastructure')
        .factory('requestInterceptor', requestInterceptor);
})()

That’s pretty simple, although it doesn’t actually do anything. Thinking about how this might display a modal wait message, we have to take care to determine when to turn the modal off or on. Like I mentioned, I like spin.js. It is simple enough to turn it off or on. Using its functionality within a simple javascript method will let us turn it on/off conditionally. I also utilize $rootscope to track the modal’s current state. I also $broadcast message to let the rest of my application know when loading is complete in the event (no pun intended) that I want a controller to take action on loading completion.

(function () {
    var requestInterceptor = function ($q, $rootScope, $injector) {
        $rootScope.submissionInProgress = false;
        $rootScope.showLoading = false;
        $rootScope.pendingRequests = 0;
        $rootScope.http = null;

        var turnOffModal = function (error) {
            $rootScope.pendingRequests--;
            $rootScope.http = $rootScope.http || $injector.get('$http');
            if ($rootScope.pendingRequests <= 0) {
                if ($rootScope.showLoading) {
                    $('body').spin("modal");
                }
                $rootScope.showLoading = false;
                if (error) {
                    $rootScope.$broadcast(error);
                }
                $rootScope.$broadcast('loading-complete');
            }
        }

        return {
            'request': function (config) {
                if (config.method !== 'GET') {
                    $rootScope.submissionInProgress = true;
                }
                $rootScope.pendingRequests++;
                if (!$rootScope.showLoading) {
                    $rootScope.showLoading = true;
                    $rootScope.$broadcast('loading-started');
                    $('body').spin("modal");
                }
                $rootScope.showLoading = true;
                return config || $q.when(config);
            },

            'requestError': function (rejection) {
                turnOffModal('request-error');
                return $q.reject(rejection);
            },

            'response': function (response) {
                if (response.config.method !== 'GET') {
                    $rootScope.submissionInProgress = false;
                }
                turnOffModal();
                return $q.when(response);
            },

            'responseError': function (rejection) {
                if (rejection.config.method !== 'GET') {
                    $rootScope.submissionInProgress = false;
                }
                turnOffModal('response-error');
                return $q.reject(rejection);
            }
        };
    };

    requestInterceptor.$inject = ['$q', '$rootScope', '$injector'];
    angular.module('myApp.infrastructure')
        .factory('requestInterceptor', requestInterceptor);
})()

Other salient points are that I turn off the modal on error and only once there are no more pending requests.

There is one minor problem with this approach and how the Angular framework, in general, behaves.   The request intercept will display the modal even if no “real” request is made.  This includes results being returned from $templateCache.

Well, problem probably isn’t the right word.  It’s by design and this behavior really makes a lot of sense.  This creates a sense of consistency throughout the Angular framework.  However, I really don’t want the modal to be displayed in the case that a template if available in $templateCache.  We can make a pretty minor modification to our request interceptor to avoid this scenario.

'request': function (config) {
	var requestUrl = config.url.toLowerCase();

	if (config.method !== 'GET') {
		$rootScope.submissionInProgress = true;
	}

	if (config.method === 'GET' && (requestUrl.indexOf('template') != -1 || requestUrl.indexOf('modal') != -1) && $templateCache.get(config.url) !== undefined) {
		$rootScope.pendingRequests++;
	}
	else {
		$rootScope.pendingRequests++;
		if (!$rootScope.showLoading) {
			$rootScope.showLoading = true;
			$rootScope.$broadcast('loading-started');
			$('body').spin("modal");
		}
		$rootScope.showLoading = true;
	}
	return config || $q.when(config);
}

This modification, obviously, requires a bit of a priori knowledge. In my case, I know that all of my templates either have the word “template” or “modal” in their name. So, if the request is a “GET” request, we detect a template is being requested, and that template is not in $templateCache, we display the modal. Otherwise, we only increment our pending requests counter.

Again, the point of this modification is so that we don’t get an annoying “flash” as the modal is toggled on and then immediately toggled off since no server request is actually made.

And there you have it.. a nice little “wait” spinner modal that will be displayed for any requests that actually go out to the server.

Leave a Reply