AngularJS: Wrapping $http For Fun and Profit
AngularJS runs its XHR HTTP requests through the $http service. It's a simple interface, easily used in your own services:
// Create an example module.
var example = angular.module('example', []);
/**
* An example service definition.
*/
function exampleService($http) {
var serviceInterface = {};
/**
* @return {object}
* A promise.
*/
serviceInterface.request = function () {
return $http.get('/endpoint');
};
/**
* @return {object}
* A promise.
*/
serviceInterface.anotherRequest = function () {
return $http(
method: 'GET',
url: '/anotherEndpoint'
).then(function (response) {
// Process a successful response before it is passed on to other code.
response.decorator = function () {};
return response;
});
};
return serviceInterface;
}
// Create the example service provider.
example.service('exampleService', ['$http', exampleService]);
The fact that all of the XHR requests from AngularJS are passing through this one service make it a useful place to apply global changes. For example, instead of injecting $http as a dependency, you could instead wrap it in your own service and use that throughout your application:
/**
* Service definition for a wrapped version of $http.
*/
function wrappedHttp($http) {
var wrapper = function () {
// Apply global changes to arguments, or perform other
// nefarious acts.
return $http.apply($http, arguments);
};
// $http has convenience methods such as $http.get() that we have
// to pass through as well.
Object.keys($http).filter(function (key) {
return (typeof $http[key] === 'function');
}).forEach(function (key) {
wrapper[key] = function () {
// Apply global changes to arguments, or perform other
// nefarious acts.
return $http[key].apply($http, arguments);
};
});
return wrapper;
}
// Create the wrapped $http service provider.
example.service('wrappedHttp', ['$http', wrappedHttp]);
// Inject the wrapper instead of $http for a specific service.
// example.service('exampleService', ['$http', exampleService]);
example.service('exampleService', ['wrappedHttp', exampleService]);
It isn't always possible or practical to define and use your own service, however. For example if you are making a late global alteration to a large ongoing project, or want to change the behavior of XHR requests in third party code that can't be altered. In these circumstances you might use $provide.decorator() to globally replace $http with a wrapped version.
var example = angular.module('example');
var provide;
// module.config() allows injection of providers but not instances.
example.config(['$provide', function ($provide) {
provide = $provide;
}]);
// module.run() allows injection of instances but not providers.
//
// You could use provide.decorator() in module.config(), but then you
// would be stuck if you needed to involve other services, as is often
// the case.
example.run(['someService', 'anotherService', function (someService, anotherService) {
provide.decorator('$http', function ($delegate) {
var $http = $delegate;
var wrapper = function () {
// Apply global changes to arguments, or perform other
// nefarious acts.
return $http.apply($http, arguments);
};
// $http has convenience methods such as $http.get() that we have
// to pass through as well.
Object.keys($http).filter(function (key) {
return (typeof $http[key] === 'function');
}).forEach(function (key) {
wrapper[key] = function () {
// Apply global changes to arguments, or perform other
// nefarious acts.
return $http[key].apply($http, arguments);
};
});
return wrapper;
});
}]);
Note that this really does mean global replacement - even for internal AngularJS activities such as loading partials. So you will most likely have to restrict your alterations to specific paths and URLs.