Use Angular $http Interceptors

When working with HTTP API of an backend, you need to handle various response from those APIs and make your own reaction on either success or failure response. But those APIs are not always designed RESTfully, It may not indicate an error status using the status code of HTTP. Instead, it may use an special field in the response body to indicate a status of an response. This implicate an burden on the developer who will not be able to use the facility provided by the framework like $http. Normally, $http() would return an promise object which can be chained with then method to handle both success or failure response. But when an HTTP request doesn’t using HTTP status code to indicate its result, your error handlers will never be called. In this case, you may need to check the response entity carefully to see whether its special field indicates its failure status.

I’v also encountered this issue when joining a new team. And I think I can’t change the API definition. So the only way to save time is do some tricky. And this issue can be easily solved by using $http interceptors.

Assume that I have an API which return an object like this.

1
2
3
4
5
6
{
"status": 0, // 0 indicates an successful request, otherwise the request is a failure one.
"data": {
}, // this is what the real data resides in.
"message": null // If status isn't 0, this message will contain the error information.
}

Well, this API definition is very straightforward though it is not follow the RESTful style. What I need do is to intercept every response made from this API and check the value of status. If it is not 0, change the statusCode of the response object to >=400. Besides, that I also want to make an global interceptor to achieve this so I can get rid of writing same check code everywhere.

Let’s start to see what $http interceptors can help.

According to AngularJS Document Interceptors are factory which can modify request and response. You need define some interceptors and push them into $httpProvider.interceptors array in your config function.

Let’s define an interceptor to check the status and reject the response if status code is not 0. Assume we have an API pattern is /api/:someTarget (:someTarget is an parameter which can be changed by need).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var restInterceptor = angular.module('RestInterceptor', []);

restInterceptor.factory('RestInterceptor', function($q) {
return {
// response function will be invoked when request responds with successful status.
// This is usually means the status code is 200~299 or an redirect response
response: function(response) {
var apiPattern = /^(?:\/api\/)\S+/;
// we need filter the config object to ensure only check our API response.
if(apiPattern.test(response.config.url) && typeof response.data.status !== 'undefined' && response.data.status !== 0) {
//here we modify the http status code to 400 indicates this should be treated as an error.
response.status = 400;
// reject this response will let the end point caller's error handler be called. also, you can
// chain an responseError interceptor to handle this error.
return $q.reject(response);
}

return response;
}
}
});

This code snippet create an interceptor to check the normal response whose HTTP status code is usually 200, but modify an response if its data.status doesn’t equal 0. and modify the HTTP status code to 400. so we can treat the API as an RESTful API and All the facility provided by angular or three party dependency can be used directly without redundant check code.

The rest job is to make the interceptor work, we need to push this factory into $httpProvider.interceptors. Note that the invoke order of response and responseError interceptors are in reverse registration order. This is not clearly documented in official document.

1
2
3
4
5
6
var app = angular.module('myApp', ['RestInterceptor']);
app.config(function($httpProvider, RestInterceptor) {
// if you have other interceptors to handle error response, make sure to push "RestInterceptor" at last.
$httpProvider.interceptors.push('OtherErrorHandlerInterceptor');
$httpProvider.interceptors.push('RestInterceptor');
})

Now you can write your code without need to check whether you got an error in your success handler.

1
2
3
4
5
6
7
8
9
10
11
12
var app = angular.module('myApp');
app.controller('SomeController', function($scope, $http){
$http.get('/api/first-item')
.success(function(data, status, headers, config) {
// write your code for successful request
// result object will be always valid.
console.log(data);
})
.error(function(data, status, headers, config) {
console.log(status); // will print 400 when data.data.status != 0
});
});

Next time, I will use interceptor to write a notification module which will automatically make a toast notification to user when an API request has failed. And will combine with the RestInterceptor I just have wrote.