Detect if Angular dependencies [angular-route, angular-resource, etc] are loaded for CDN fallback

All we need is an easy explanation of the problem, so here it is.

I’m using Angular JS on ASP.NET MVC 4 and I am using script bundles to load from a cdn and also load from the origin server in case of a cdn failure like so:

var jQuery = new ScriptBundle("~/bundles/scripts/jquery",
            "//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js") // CDN
            .Include("~/Scripts/jquery-{version}.js");  // Local fallback
jQuery.CdnFallbackExpression = "window.jQuery"; // Test existence
bundles.Add(jQuery);

and

var angular = new ScriptBundle("~/bundles/scripts/angular",
            "//ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.2/angular.min.js")
            .Include("~/Scripts/angular.js");
angular.CdnFallbackExpression = "window.angular";
bundles.Add(angular);

It is fairly easy to detect if jQuery or AngularJS exist using window.jQuery and window.Angular respectively. The ASP.NET bundling mechanism evaluates the CdnFallbackExpression text to see if it needs to fall back to the origin server.

However, in later versions of AngularJS, other modules such as ngRoute and ngResource are separated into their own files to be loaded at the developers discretion.

How do I detect if other AngularJS modules are loaded? What could I type in the console to see if ngAnimate, ngRoute, ngResource, etc. succeeded in loading from the CDN?

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

Here is an approach that works specifically with the Microsoft Optimization Framework as you provided in the OP

angularjsRoute.CdnFallbackExpression = @"
    function() { 
        try { 
            window.angular.module('ngRoute');
        } catch(e) {
            return false;
        } 
        return true;
    })(";

This expression is not valid javascript itself, but the MS Optimization Framework uses this and eventually produces the following output to the page. Now we have a valid self-executing javascript function that returns true or false based on whether the angular module loads.

<script>(
function() { 
    try {
        window.angular.module('ngRoute');
    }
    catch(e) {
        return false;
    }

    return true;
})()||document.write('<script src="/bundles/scripts/angularjs-route"><\/script>');</script>

Method 2

This is what I use:

<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.5.0/ui-bootstrap.min.js"></script>
<script>
  try { //try to load from cdn
    //use the name of the angular module here
    angular.module('ui.bootstrap');
  } 
  catch(e) { //error thrown, so the module wasn't loaded from cdn
    //write into document from local source
    document.write('<script src="sys/lib/ui-bootstrap.js"><\/script>'); 
  }
</script>

angular.module throws an error if there is no such module, which is exactly what we need to know! try/catch is great here.

Method 3

(Variation on the answer from qntmfred.)

Instead of leaving that strange trailing open bracket, just use a normal immediately-invoked function.

The result will simply be that the Optimization Framework will wrap this in another set of parentheses, but leaves your C# much clearer.

angularjsRoute.CdnFallbackExpression = 
    @"(function() { 
        try { 
            window.angular.module('ngRoute');
        } catch(e) {
            return false;
        } 
        return true;
    })()";

Method 4

Another variation…

<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.14.3/ui-bootstrap-tpls.min.js"></script>
<script>
    try {
        window.angular.module('ui.bootstrap');
    }
    catch(e) {
        var script = document.createElement('script');
        script.src = 'lib/bootstrap/dist/js/bootstrap.js';
        document.getElementsByTagName('head')[0].appendChild(script);
    }
</script>

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply