Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS 1.3 page won't load in IE8

As an angular user, I too shudder at the title of this question, due to the fact that IE8 is evil incarnate and should be put down like a rabid dog.

that being said, I was wondering if anyone else has run into the issue of loading Angular 1.3 in IE8, and the page breaking before load and just reporting an error: Object Expected on an if condition that uses the isArray() function. (isArray() is also found in Angular 1.2, so it confuses me that it works there but not in 1.3)

So that everyone understands my reasons for this, my company recently took the step of no longer supporting IE8 on new development. But our new UI needs to support IE8 on the initial landing page ONLY, so that users can still access our old software that supports IE8. I was hoping that I could use 1.3, and just write small tweaks for the landing page until it is also out from underneath IE8.

Overarching question: is it possible to use Angular 1.3 with IE8 at all, or will I be forced to use 1.2 until we completely remove IE8 support?

like image 275
SamHuckaby Avatar asked Nov 17 '14 17:11

SamHuckaby


2 Answers

There is a way, though it's a bit rough. Below is the code you need to load before angular and your app may run. This is a collection of shims/polyfills, mostly from Mozilla Developer Network some by me.

Please note, that this only allows AngularJS to run, it doesn't update the JS runtime of IE8. So things like somePromise.catch(...) won't work, you must write somePromise["catch"](...).

if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function(searchElement) {
        if (this.length === 0) {
            return -1;
        }
        var n = 0;
        if (arguments.length > 1) {
            n = Number(arguments[1]);
            if (isNaN(n)) {
                n = 0;
            } else if (n !== 0 && n !== Infinity && n !== -Infinity) {
                n = (n > 0 || -1) * Math.floor(Math.abs(n));
            }
        }
        if (n >= this.length) {
            return -1;
        }
        var k = n >= 0 ? n : Math.max(this.length - Math.abs(n), 0);
        while (k < this.length) {
            if (k in this && this[k] === searchElement) {
                return k;
            }
            ++k;
        }
        return -1;
    };
}

if (!Array.prototype.filter) {
    Array.prototype.filter = function(fun/*, thisArg*/) {
        if (this === undefined || this === null) {
            throw new TypeError();
        }

        var t = Object(this);
        var len = t.length >>> 0;
        if (typeof fun !== 'function') {
            throw new TypeError();
        }

        var res = [];
        var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
        for (var i = 0; i < len; i++) {
            if (i in t) {
                var val = t[i];
                if (fun.call(thisArg, val, i, t)) {
                    res.push(val);
                }
            }
        }
        return res;
    };
}

if (!Array.isArray) {
    Array.isArray = function(arg) {
        return Object.prototype.toString.call(arg) === '[object Array]';
    };
}

if (!Array.prototype.every) {
    Array.prototype.every = function(callbackfn, thisArg) {
        'use strict';
        var T, k;
        if (this == null) {
            throw new TypeError('this is null or not defined');
        }
        var O = Object(this);
        var len = O.length >>> 0;
        if (typeof callbackfn !== 'function') {
            throw new TypeError();
        }
        if (arguments.length > 1) {
            T = thisArg;
        }
        k = 0;
        while (k < len) {

            var kValue;

            if (k in O) {
                kValue = O[k];
                var testResult = callbackfn.call(T, kValue, k, O);
                if (!testResult) {
                    return false;
                }
            }
            k++;
        }
        return true;
    };
}

if (!Object.create) {
    Object.create = (function() {
        var Object = function() {};
        return function (prototype) {
            if (arguments.length > 1) {
                throw new Error('Second argument not supported');
            }
            if (typeof prototype != 'object') {
                throw new TypeError('Argument must be an object');
            }
            Object.prototype = prototype;
            var result = new Object();
            Object.prototype = null;
            return result;
        };
    })();
}

if (!Array.prototype.forEach) {
    Array.prototype.forEach = function(fun /*, thisArg */) {
        if (this === void 0 || this === null)
            throw new TypeError();

        var t = Object(this);
        var len = t.length >>> 0;
        if (typeof fun !== "function")
            throw new TypeError();

        var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
        for (var i = 0; i < len; ++i) {
            if (i in t)
                fun.call(thisArg, t[i], i, t);
        }
    };
}

if (!String.prototype.trim) {
    String.prototype.trim = function() {
        return this.replace(/^\s+|\s+$/gm, '');
    };
}

(function() {
    //$http uses onload instead of onreadystatechange. Need shimming as IE8 doesn't have onload.
    if (new XMLHttpRequest().onload === undefined) {
        var orig = XMLHttpRequest.prototype.send;
        XMLHttpRequest.prototype.send = function() {
            var self = this;
            if (!this.onreadystatechange && this.onload) {
                this.onreadystatechange = function() {
                    if (self.readyState === 4) {
                        self.onload();
                    }
                };
            }
            orig.apply(self, arguments);
        };
    }
})();

if (!Date.now) {
    Date.now = function() {
        return new Date().getTime();
    };
}

if (!Function.prototype.bind) {
    Function.prototype.bind = function(oThis) {
        if (typeof this !== "function") {
            throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
        }
        var aArgs = Array.prototype.slice.call(arguments, 1),
            fToBind = this,
            fNOP = function() {
            },
            fBound = function() {
                return fToBind.apply(this instanceof fNOP && oThis
                        ? this
                        : oThis,
                    aArgs.concat(Array.prototype.slice.call(arguments)));
            };

        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();

        return fBound;
    };
}

if (!Object.keys) {
    Object.keys = function(object) {
        var keys = [];
        for (var o in object) {
            if (object.hasOwnProperty(o)) {
                keys.push(o);
            }
        }
        return keys;
    };
}

if (!Object.getPrototypeOf) {
    Object.getPrototypeOf = function(object) {
        return object.__proto__ || object.constructor.prototype;
    };
}

If you have angular-bootstrap, you also need to "patch" the angular.min.js file, because angular-boostrap uses {in: someCondition}, but because of the older JS runtime the in keyword is reserved and will fail in the code generation.

Find: var e=(b?"s":'((l&&l.hasOwnProperty("'+a+'"))?l:s)')+"."+a;

Replace: var e=(b?"s":'((l&&l.hasOwnProperty("'+a+'"))?l:s)')+"['"+a+"']";

like image 86
Zenorbi Avatar answered Oct 18 '22 01:10

Zenorbi


Per the comments on the question, and Zenorbi's answer, Angular 1.3 does NOT load correctly in IE8 anymore. It was never designed to continue working in IE8, so this should not come as a surprise.

I actually came up with a simple workaround that will make page load times slightly slower for any IE8 users which is an acceptable loss for me.

using this code, I can simply load 1.3 by default, and if any IE8 users load the page, it will simply load angular 1.2 directly afterwards, simply overwriting any duplicated code:

<script type="text/javascript" src="target/libraries/angular.min.js"></script>
<!--[if lt IE 9]>
<script type="text/javascript" src="target/libraries/angular-1.2.min.js"></script>
<![endif]-->
<script type="text/javascript" src="target/libraries/angular-route.min.js"></script>
<!--[if lt IE 9]>
<script type="text/javascript" src="target/libraries/angular-route-1.2.min.js"></script>
<![endif]-->

note: This is terrible practice generally. If we were making a larger effort to support IE8 users, I would go with Zenorbi's answer, since it allows you to load only one version of angular.

like image 5
SamHuckaby Avatar answered Oct 18 '22 01:10

SamHuckaby