AngularJS 1.3 page won't load in IE8

2019-03-21 03:13发布

问题:

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?

回答1:

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+"']";



回答2:

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.



回答3:

Based on answers from SamHuckaby and Zenorbi, i came up with acceptable solution which is a combination of their ideas and insights from this article IF Internet Explorer THEN Do Something Else (A How To...) by Phil Nash:

     <!--[if !IE]>-->
        <script src="/ui/resources/webjars/angularjs/1.4.0/angular.js"></script>
        <script src="/ui/resources/webjars/angularjs/1.4.0/angular-route.js"></script>
    <!--<![endif]-->

    <!--[if gt IE 8]>
        <script src="/ui/resources/webjars/angularjs/1.4.0/angular.js"></script>
        <script src="/ui/resources/webjars/angularjs/1.4.0/angular-route.js"></script>
    <![endif]-->

    <!--[if lt IE 9]>
        <script type="text/javascript" src="/ui/resources/lib/angularjs/1.2.28/angular.js"></script>
        <script type="text/javascript" src="/ui/resources/lib/angularjs/1.2.28/angular-route.js"></script>
    <![endif]-->

        <script type="text/javascript" src="webjars/es5-shim/4.0.6/es5-shim.js"></script>
        <script type="text/javascript" src="webjars/es6-shim/0.20.2/es6-shim.js"></script>

<!--[if !IE]>-->...<!--<![endif]--> - Conditional comment will be evaluated by IE, but the scripts within won't get loaded by IE and will get loaded by all other browsers

<!--[if gt IE 8]>...<![endif]--> - Conditional comment will be evaluated by IE and if greater than IE 8, scripts will get loaded

<!--[if lt IE 9]>...<![endif]--> - Conditional comment will be evaluated by IE and if less than IE 9, scripts will get loaded.

Both es5-shim and es6-shim should be put out of IE8 conditional comment block and checked by all browsers (and it's not expensive operation) as i recently found out that Safari has issues with String.prototype.startsWith()

Yeah we have some code duplication inside the first and the second conditional comment (and it can't be solved in a different way) but we have zero unwanted scripts loaded and we can close our task here.



回答4:

From Angular Developer Guide migration docs:

Note: AngularJS 1.3 is dropping support for IE8. Read more about it on our blog. AngularJS 1.2 will continue to support IE8, but the core team does not plan to spend time addressing issues specific to IE8 or earlier.



回答5:

Github user @fergaldoyle maintains a repo that combines shiming/polyfill and other patch strategies in order to maintain compatibility.

This could be a viable strategy for many solution providers



回答6:

I tried L0lander's answer (which was my preferred one), but when using older angular version, other scripts complained, and it ended up not working anyway. So, I checked statistics at my site, and 0,2% only use IE8 or less, and I won't be giving myself headache over such a small crowd, so I simply added a message asking users of IE8 or less to upgrade.

Add following code just after body tag to all of your pages:

<!--[if lt IE 9]>
    <div style="text-align: center; font-size: 22px; padding: 20px; background-color: #d14c4c; color: #f3e3e3;">Your version of Internet Explorer is too old for this site to function properly.<br>Please <a href="https://www.google.com/search?q=update+internet+explorer" target="_blank" style="text-decoration: underline; color: #76c880">click here</a> to upgrade to a newer version.</div>
<![endif]-->