CDNs and Fallbacks for Angular Modules

I’ve posted before about CDNs and fallbacks in the context of ASP.NET 5 and MVC 6. I discussed the whys and wherefores, so please read the aforementioned post if you want to understand the details of why CDNs and fallbacks are recommended practice when consuming javascript resources in your web applications.

Bottom line is that consuming javascript libraries from CDNs improves performance both on the web server and on the client browsers. But CDNs don’t come with SLAs and so there is no guarantee that the CDN will be operational when a client requests a resource. We therefore need to include a fallback position which directs the client to request the resource from our own servers when the CDN has failed.

Basic Fallback Test

<!-- Load AngularJS from Google CDN -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<!-- Fallback to local file -->
<script>window.angular || document.write('<script src="scripts/angular.min.js">\x3C/script>')</script> 

The above script attempts to load the angular.min.js library from the Google CDNs.

It then executes a simple OR statement which checks to see if the windows.angular object resolves to true or false.

True indicates that the object exists and the library was successfully loaded from the Google CDN. The OR statement is short-circuited and all is well.

False indicates that the library failed to load. The expression on the second half of the OR statement is resolved, and a script element is written to the document object, directing the browser to request the library from our servers.

Note. All javascript objects can be evaluated in a boolean context, and will either evaluate to true (truthy values) or evaluate to false (falsy values). Falsy values include 0, null, undefined, false, “”, and NaN. All other values are truthy.

In short, the fallback position should check for the existence of a globally scoped object specific to the library in question. And if the object does not exist, then direct the browser to request the library from our servers.

But what if the library does not define any globally scoped objects, and instead only extends objects from another library that it depends on. This is the case with many of the angular UI libraries which extend the windows.angular object by injecting modules. In these cases we need to write a javascript statement that checks for the existence of modules and evaluates to true.

Complex Fallback Test

<!-- Load Angular Bootstrap UI from Google CDN -->
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.3/ui-bootstrap.min.js"></script>
<!-- Fallback to local file -->
<script>
    (function () {
        try {
            window.angular.module('ui.bootstrap');
        }
        catch (e) {
            return false;
        }
        return true;
    })() || document.write('<script src="scripts/ui-bootstrap.min.js">\x3C/script>')
</script>

In the example above, the fallback position uses a self-invoking anonymous function to check to see if the ui.bootstrap module has been injected into the window.angular.module collection. The function incorporates a try and catch block as attempting to reference a module that does not exist results in an errorThe catch block returns a false.if the module does not exist. Otherwise the function returns a true.

ASP.NET MVC 5 FallBack Test

When registering bundles for minification we can specify a CDN path and fallback position.

The ScriptBundle class has 2 arguments, the second of which we can use to register a CDN path. In addition we can assign a fallback expression string to the cdnFallbackExpression property – which I have done in the examples below using object initialization syntax.

If we then set the bundles.UseCdn property to true, and ensure that the web.config compilation debug flag is set to false – the CDN paths will be used to serve up script libraries, and the fallback expressions will be utilized to provide a fallback position.

using System.Web;
using System.Web.Optimization;

namespace MVC5TestApp
{
    public class BundleConfig
    {
        public static void RegisterBundles(BundleCollection bundles)
        {
            bundles.Add(
                new ScriptBundle(
                    "~/bundles/angular",
                    "//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.3/angular.min.js") { CdnFallbackExpression = "window.angular" }
                    .Include("~/Scripts/angular.js"));

            bundles.Add(
                new ScriptBundle(
                    "~/bundles/angular-ui-bootstrap",
                    "//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.3/ui-bootstrap2.min.js")
                        {
                            CdnFallbackExpression = @"   
                                (function () {
                                    try {
                                        window.angular.module('ui.bootstrap');
                                    }
                                    catch (e) {
                                        return false;
                                    }
                                    return true;
                                })()"
                        }
                    .Include("~/Scripts/angular-ui/ui-bootstrap.js"));

            bundles.UseCdn = true;
        }
    }
}

 ASP.NET MVC 6 FallBack Test

The Environment and Script TagHelper classes and attributes were introduced with MVC 6. They enable us to specify different sources for script, and fallback tests, based on the execution environment. For more details follow the link at the top of this post.

<environment names="Development">
    <script src="~/lib/angular/angular.js"></script>
    <script src="~/lib/angular-bootstrap/ui-bootstrap.js"></script>
</environment>
<environment names="Staging,Production">
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.3/angular.min.js"
            asp-fallback-src="~/lib/angular/angular.min.js"
            asp-fallback-test="window.angular">
    </script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.3/ui-bootstrap.min.js"
            asp-fallback-src="~/lib/angular-bootstrap/ui-bootstrap.min.js"
            asp-fallback-test="
                (function() {
                    try {

                        window.angular.module('ui.bootstrap');
                    } catch(e) {
                        return false;
                    }
                    return true;
                })()">
    </script>
</environment>
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s