Many would agree with me that to create a website to run on IE is a headache chore. I would have dropped the IE browser if we were given that choice. Our team has decided to use Angular as our primary html platform. It works great and we all loved it, until the app started to break in IE, which is caused by lack of IE cors (cross-origin-resource-sharing) support. Angular had a solution to use XDomainRequest, but removed due to its limited capabilities. Fortunately, there’s a better solution. Let’s go over the steps:
First, we have to hijack the $http service. So that it can support IE CORS when detected. On the main application where Angular is expected to access the resources (templates/API) on another server, we will modify the angular.js file to use our very own httpBackendProvider. Find the $HttpBackendProvider function and replace it as below (roughly between 9200 to 9300 line, for v1.0.7)
function $HttpBackendProvider() {
this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) {
var params = [$browser, XHR, $browser.defer, $window.angular.callbacks, $document[0], $window.location.protocol.replace(':', '')]; /*orig xhr */
var param4ie = params.concat([msie,createHttpBackend.apply(this,params)]);
return (angular.ieCreateHttpBackend && angular.ieCreateHttpBackend.apply(this, param4ie)) ||
createHttpBackend.apply(this, params);
}];
}
2nd step is to create the ieCreateHttpBackend method and that’s where we put the override logic for IE CORS support. see the code below, we are extending angular namespace with the new function ieCreateHttpBackend. Lets save it as angular.ieCors.js, be sure to wrap it as a self invoking function.
window.angular = {
ieCreateHttpBackend: function ($browser, XHR, $browserDefer, callbacks, rawDocument, locationProtocol, msie, xhr) {
if (!msie || msie > 9) return null;
var getHostName = function (path) {
var a = document.createElement('a');
a.href = path;
return a.hostname;
}
var isLocalCall = function (reqUrl) {
var reqHost = getHostName(reqUrl),
localHost = getHostName($browser.url());
patt = new RegExp( localHost + "$", 'i');
return patt.test(reqHost);
}
function completeRequest(callback, status, response, headersString) {
var url = url || $browser.url(),
URL_MATCH = /^([^:]+):\/\/(\w+:{0,1}\w*@)?(\{?[\w\.-]*\}?)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/;
// URL_MATCH is defined in src/service/location.js
var protocol = (url.match(URL_MATCH) || ['', locationProtocol])[1];
// fix status code for file protocol (it's always 0)
status = (protocol == 'file') ? (response ? 200 : 404) : status;
// normalize IE bug (http://bugs.jquery.com/ticket/1450)
status = status == 1223 ? 204 : status;
callback(status, response, headersString);
$browser.$$completeOutstandingRequest(angular.noop);
}
var pmHandler = function (method, url, post, callback, headers, timeout, withCredentials) {
var win = $('[name="' + getHostName(url) + '"]')[0].id ;
console.log('ie postMessage for url : ' + url);
console.log( 'iframe window ' + win);
pm({
target: window.frames[win],
type: 'xhrRequest',
data: {
headers: headers,
method: method,
data: post,
url: url
},
success: function (respObj) {
completeRequest(callback, 200, respObj.responseText, 'Content-Type: ' + respObj.contentType);
},
error: function (data) {
completeRequest(callback, 500, 'Error', 'Content-Type: text/plain');
}
});
}
return function (method, url, post, callback, headers, timeout, withCredentials) {
$browser.$$incOutstandingRequestCount();
url = url || $browser.url();
if (isLocalCall(url) ) {
xhr(method, url, post, callback, headers, timeout, withCredentials);
} else {
pmHandler(method, url, post, callback, headers, timeout, withCredentials);
}
if (timeout > 0) {
$browserDefer(function () {
status = -1;
xdr.abort();
}, timeout);
}
}
}
};
The entire IE CORS solution is base on postMessage.js , and it uses an invisible iframe to make XmlHttpRequest to another server. So we have to setup an iframe in the body
As indicated above, we are expecting the remote server to receive the request via the ClientProxy page. We will have to setup the page later.
Next step is to make sure the js files are loaded properly. Due to the way how it is injected into Angular, be sure to load angular.ieCors.js prior to angular.js
<script src="/Scripts/jquery-1.10.2.js"></script>
<script src="/Scripts/postMessage.js"></script>
<script src="/Scripts/angular.ieCors.js"></script>
<script src="/Scripts/angular.js"></script>
Now let’s switch our focus to the ClientProxy page, which is hosted on the remote server where the resources are (html templates, api, etc). In order for CORS to work, the resource server must reply with the access-control origin headers. “*”, a wildcard origin, should be used with cautious. These headers basically tell client browser to enable CORS for the http requests.
Access-Control-Allow-Headers:Accept, Content-MD5, Content-Type, X-Requested-With
Access-Control-Allow-methods:POST, GET, OPTIONS, PUT, DELETE
Access-Control-Allow-Origin:*
Access-Control-Max-Age:1728000
Here’s the code for ClientProxy
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="/Scripts/jquery-1.10.2.js"></script>
<script src="/Scripts/postMessage.js"></script>
<script type="text/javascript">
(function () {
pm.bind("xhrRequest", function (data) {
console.log(JSON.stringify(data));
if (data && data.url) {
var ret = $.ajax({
type: data.method,
url: data.url,
headers: data.headers,
data: data.data, //forgot to include the body
async: false
});
return {
responseText: ret.responseText,
contentType: ret.getResponseHeader("Content-Type")
};
}
return '';
});
})();
</script>
</head>
<body style="color:#3e3e3e">
This page is intentionally left blank
</body>
</html>
That’s pretty much it. The final point I should make is that jQuery has stopped supporting IE8/9 with v2.0; it maybe good to stick with v1.10.2