AngularĀ $http Service
Matt Zabriskie
@mzabriskie
Source: http://www.wallarc.com/wallpaper/view/704155
History of XHR
In the beginning was the full page reload,
and the full page reload was total crap
Full Page Reload
<!doctype html>
<html>
<head>
<title>My Site</title>
<link rel="stylesheet" type="text/css" href="styles.css"/>
</head>
<body>
<div class="container">
<div class="header">...</div>
<div class="content">
...
<form method="POST" action="/path/to/handler.php">
...
<button type="submit">Submit</button>
</form>
...
</div>
<div class="footer">...</div>
</div>
<script type="text/javascript" src="scripts.js"></script>
</body>
</html>
Full Page Reload
- Easy to implement
- Blank screen while loading
- Poor performance
- Reload layout, styles, scripts
Fake-jax
<form method="POST" action="/path/to/handler.php"
target="formHandler" onsubmit="return false;">...</form>
<iframe id="formHandler" onload="handleIframeLoad();"
style="width: 0; height: 0;"></iframe>
<script type="text/javascript">
function handleIframeLoad() {
// Parse response from iframe DOM
}
</script>
Fake-jax
- No indication of progress
- Parsing iFrame DOM is ugly
- XML is very verbose
XMLHttpRequest
- Originated within Microsoft for Exchange Server 2000
- Shipped with Internet Explorer 5.0 in March 1999 (ActiveX)
- Fully functional XMLHttpRequest landed in Mozilla in 2002
- Became de facto standard between 2004-2005
- Internet Explorer 7.0 added XMLHttpRequest object
XMLHttpRequest
var factory = [
function () {return new XMLHttpRequest()},
function () {return new ActiveXObject("Msxml2.XMLHTTP")},
function () {return new ActiveXObject("Msxml3.XMLHTTP")},
function () {return new ActiveXObject("Microsoft.XMLHTTP")}
];
function getXHR() {
var xhr = null;
for (var i=0, l=factory.length; i<l; i++) {
try { xhr = factory[i](); } catch (e) { continue; }
break;
}
return xhr;
}
var request = getXHR();
request.open('POST', '/path/to/handler', true);
request.setRequestHeader('Content-type','application/x-www-form-urlencoded');
request.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.status >= 200 && request.status < 300) {
alert('success');
} else {
alert('error');
}
}
};
request.send('foo=bar');
XMLHttpRequest
- Still no progress indicator
- Messing with ActiveX variations sucks
- Does anyone still use XML?
window.fetch
fetch('/users.json')
.then(function(response) {
return response.json();
}).then(function(json) {
console.log('parsed json', json);
})
.catch(function(ex) {
console.log('parsing failed', ex);
});
window.fetch
- Replacement for XHR
- Currently being developed by Chrome
- Available as a polyfill
$http
- $q
- $httpBackend
- $cacheFactory
Under the Hood
angular.module('app', [])
.controller('MyCtrl', function($scope, $http) {
$http({
url: 'https://api.github.com/users/mzabriskie'
}).then(function(res) {
$scope.user = res.data;
}).catch(function (res) {
$scope.error = res.status + ' (' + res.statusText + ')';
});
});
Usage
Config
$http({
url: '/some/url',
method: 'get', // default
params: {foo: 'bar'}, // ?foo=bar
data: {name: 'Fred'}, // POST body
headers: {'X-Requested-With': '$http'},
xsrfHeaderName: 'X-XSRF-TOKEN', // default
xsrfCookieName: 'XSRF-TOKEN', // default
transformRequest: function (data, headers) {}, // default JSON serializer
transformResponse: function (data, headers) {}, // default JSON deserializer
cache: false, // default
timeout: 1000,
withCredentials: false, // default
responseType: 'json'
});
Response
$http({
url: '/some/url'
})
.then(function (res) {
// Data that came back as the response body
console.log(res.data);
// Response headers (object)
console.log(res.headers);
// Response status (200)
console.log(res.status);
// Config that was used for the request
console.log(res.config);
});
Shortcuts
// $http.get(url[, config])
$http.get('/some/url').then(function (res) {});
// $http.post(url, data[, config])
$http.post('/some/url', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (res) {});
Defaults
angular.module('app', [])
.config(function ($httpProvider) {
// Set default xsrfHeaderName for all requests
$httpProvider.defaults.xsrfHeaderName = 'MY-XSRF-TOKEN';
// Add custom request header to all requests
$httpProvider.defaults.headers.common['My-Custom-Header'] = 'something amazing';
});
Transformers
Transformers
- Transform both requests & responses
- Transform data sent/received
- Transform headers
Built-in Transformers
- Transform data of requests to JSON
- Transform data of responses from JSON
- Protection against JSON vulnerabilities
Custom Transformers
angular.module('app', [])
.config(function ($httpProvider) {
// Transform response data to convert ISO-8601 date format to Date object
var ISO_8601 = /(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})Z/;
$httpProvider.defaults.transformResponse.push(function (data, headers) {
for (var k in data) {
if (data.hasOwnProperty(k)) {
if (ISO_8601.test(data[k])) {
data[k] = new Date(Date.parse(data[k]));
}
}
}
return data;
});
})
Gotcha
angular.module('app', [])
.config(function ($httpProvider) {
$httpProvider.defaults.transformRequest = function (data, headers) {
if (typeof data.firstName !== 'undefined') {
data.firstName = 'Joe';
}
return data;
};
})
.controller('MyCtrl', function($scope, $http) {
$scope.user = {};
$scope.addUser = function (user) {
$http.post('/users', angular.toJson(user))
.then(function(res) {
$scope.response = res;
$scope.user = {};
});
};
});
Workaround
function appendTransform(defaults, transform) {
// We can't guarantee that the default transformation is an array
defaults = angular.isArray(defaults) ? defaults : [defaults];
// Append the new transformation to the defaults
return defaults.concat(transform);
}
$http({
url: '/some/url',
method: 'GET',
transformResponse: appendTransform(
$http.defaults.transformResponse,
function(value) {
return doTransform(value);
}
)
});
Interceptors
Interceptors
- Intercept request before it's dispatched
- Intercept response after it returns
- Intercept request/response errors
- Modify the config for the request
- Modify the response object
Simple Interceptor
angular.module('app', [])
.config(function ($httpProvider) {
$httpProvider.interceptors.push('loggingInterceptor');
})
.factory('loggingInterceptor', function ($log) {
return {
request: function (config) {
$log.info(config.method + ' ' + config.url);
return config;
}
};
})
Log Request Time
angular.module('app', [])
.config(function ($httpProvider) {
$httpProvider.interceptors.push('timestampInterceptor');
})
.factory('timestampInterceptor', function () {
return {
request: function (config) {
config.requestTime = Date.now();
return config;
},
response: function (response) {
response.config.responseTime = Date.now();
return response;
}
}
})
.controller('MyCtrl', function($http, $log) {
$http.get('https://api.github.com/users/mzabriskie/repos'),
.then(function (res) {
var time = res.config.responseTime - res.config.requestTime;
$log.info('Request took ' + (time / 1000) + ' seconds');
});
});
Recover Session
module.config(function($httpProvider) {
$httpProvider.interceptors.push('sessionRecoverer');
});
module.factory('sessionRecoverer', function($q, $injector) {
return {
responseError: function(response) {
// Session has expired
if (response.status === 419){
var SessionService = $injector.get('SessionService');
var $http = $injector.get('$http');
var deferred = $q.defer();
// Create a new session (recover the session)
SessionService.login().then(deferred.resolve, deferred.reject);
// When the session recovered, make the same backend call again
return deferred.promise.then(function() {
return $http(response.config);
});
}
return $q.reject(response);
}
};
});
Caching
Caching
- Allows caching response data
- Only works with GET method
- Supports custom cache
Simple Cache
angular.module('app', [])
.controller('MyCtrl', function ($scope, $http) {
$http.get('/some/url', { cache: true });
});
Custom Cache
angular.module('app', [])
.factory('customCache', function ($cacheFactory) {
return $cacheFactory('custom');
})
.controller('MyCtrl', function ($scope, $http, customCache) {
$scope.loadData = function () {
$http.get('/some/url', { cache: customCache })
.then(function (res) {
$scope.data = res.data;
});
};
$scope.clearData = function () {
customCache.remove('/some/url');
};
});
Concurrency
$q.all
angular.module('app', [])
.controller('MyCtrl', function($scope, $http, $q) {
$q.all([
$http.get('/user/12345'),
$http.get('/user/12345/permissions')
]).then(function (res) {
$scope.user = res[0].data;
$scope.perm = res[1].data;
});
});
Resources
Thank You!
@mzabriskie
Angular $http Service
By Matt Zabriskie
Angular $http Service
My talk for AngularJS Utah October 2014
- 3,241