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.