The Well-Tempered AngularJS Web Part

Listen while reading …

Manuscript for Bach's Fugue in Ab Major
Manuscript for Bach’s Fugue in Ab Major

The mathematics of music isn’t as simple as you might think, and early musical instruments often didn’t get it right. Keyboard instruments were especially susceptible to this. On a particular organ, for example, the fifth interval in the key of C might sound harmonious, but the fifth above Ab might be sour and grating to the ear. These so-called “wolf” notes could spoil a whole composition. It wasn’t until the late 17th century that “well-tempered” instruments came along, a fact that was celebrated by J.S. Bach in his solo keyboard composition, “The Well-Tempered Clavier,” which includes harmonious preludes and fugues in every possible key.

Like notes on a piano, web parts (or any kind of web widgets) are combined in new and unexpected ways on a page. Yet sometimes they don’t play well together. This article will show you how to write “well-tempered” web parts that always behave appropriately, even if other versions of Angular are used on the page.

Recently I’ve been using AngularJS to build web parts that run in the browser, and I’ve noticed that every example I’ve found on the Internet assumes it’s the only Angular web part on the page. Add a second instance of the web part, or another web part that uses Angular, and they will clash in unpredictable ways. This might not be a problem in a SharePoint App where each web part runs on its own page in an IFrame, but it can cause real dissonance if web parts are running directly on a web page. This can happen in a Content Editor or Script Editor web part using Remote Provisioning, or a Visual Web Part in a farm or sandboxed solution.

A typical Angular application begins like this:

<script src="angular.1.3.14.min.js"></script>
<script src="mainController.js"></script>
<div ng-app="WeatherApp">
  <div ng-controller="Main">
    <h1>{{City}} Weather</h1>
    <div ng-include="weatherDisplay.html" 
         ng-show="ValidDataLoaded"></div>
    <p class="error">{{error}}</p>
  </div>
</div>

Notice the ng-app attribute on line 3. This powerful directive bootstraps Angular, but the Angular documentation clearly states that you can only have one of these on a page. This is the first issue. The other is that if a web part adds the Angular script library to a page it will overwrite any Angular libraries in use elsewhere. The ideal solution will handle both of these issues by:

  • Only loading Angular if it’s necessary
  • Checking to see if an earlier version of Angular is on the page, and if so, loading the version we want while preserving the other version
  • Binding Angular to a relative position on the page rather than a fixed directive or element ID which won’t work if it’s duplicated

In this scenario, the HTML changes to this:

<div>
  <div ng-controller="Main">
    <h1>{{City}} Weather</h1>
    <div ng-include="weatherDisplay.html"
         ng-show="ValidDataLoaded"></div>
    <p class="error">{{error}}</p>
  </div>
  <script src="initUI.js"></script>
</div>

Notice that the ng-app directive is gone, as are the script tags to load Angular and mainController.js. Instead, a new script called initUI.js loads Angular and the other scripts, and “bootstraps” angular. Specifically, it binds Angular to the parent of the <script> element, so it’s just like placing ng-app on the parent except you can use it many times on the page.

Here’s the initUI script:

(function () {
    'use strict';

    // loadScript() - Function to dynamically load a script
    function loadScript(url, onload) {
        var head = document.getElementsByTagName('head')[0];
        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.onreadystatechange = function () {
            if (this.readyState == 'complete') onload();
        };
        script.onload = onload;
        script.src = url;
        head.appendChild(script);
    }


    // loadApp() - Function to load the application after Angular is ready
    function loadApp(myAngular, scriptPath, elementToBind)
    {
        loadScript(scriptPath +'mainController.js', function () {
            // Bind the main controller
            myAngular.bootstrap(elementToBind, ['microSurvey']);
        });
    }

    // *** In-line execution begins here ***

    // Find element to bind Angular to
    var element = document.documentElement;
    while (element.childNodes.length && element.lastChild.nodeType == 1) {
        element = element.lastChild;
    }
    var elementToBind = element.parentNode;
    var scriptPath = element.src;
    scriptPath = scriptPath.substring(0, scriptPath.lastIndexOf('/')+1);

    // Check for old version of Angular on the page; if found, save it awway.
    var tempAngular = null;
    if (window.angular) {
        if (angular.version.major < 1 || angular.version.minor < 3) {
            tempAngular = angular;
            angular = null;
        }
    }

    // Now check for Angular on the page. If it's here, it must be an OK version.
    // If not, load it.
    if (!window.angular) {
        loadScript(scriptPath + 'angular.1.3.14.min.js', function () {

            loadApp(angular, scriptPath, elementToBind);
            if (tempAngular) {
                angular = tempAngular;
            }
        });
    } else {
        loadApp(angular, scriptPath, elementToBind);
    }

})();
Lines 1 and 61: An Immediately Invoked Function Expression

This little construct, sometimes called an “IIFE” (sounds like “iffy”), shows up a lot in well composed JavaScript solutions.

(function() {
    // YOUR CODE HERE
})();

It may seem a little odd at first: it’s an anonymous function that’s called only once, right where it’s defined. What’s the point?

Well, variables and functions in JavaScript are placed in the global namespace by default; in a web browser, that’s the window object. As in most programming scenarios, globals are a bad idea since they can conflict with other programs. Variables and functions declared in a function are scoped to that function only. By wrapping the code in a function, the variables and functions don’t enter the global namespace. JavaScript closures ensure these symbols are available when contained functions are called later on.

By the way, Angular itself adds exactly one symbol to the global namespace, and that’s the angular object, which is used for all JavaScript access to the library.

Lines 4-15: loadScript() function

This is a function that loads a script, and invokes a callback function when the script has loaded. It’s the equivalent of the jQuery $.getScript() call. You can certainly use jQuery for this, but I decided to keep this solution “pure” and use Angular as my only library. Thanks to Jan Wolter for this measure of JavaScript goodness.

Lines 18-25: loadApp() function

This function uses loadScript() to load the application JavaScript after Angular has loaded. It accepts the angular object as an argument; this is necessary in case we loaded our own version of Angular and don’t want to use whatever version someone else left laying around.

Once the script is loaded, its callback function invokes Angular’s bootstrap method to explicitly attach to the specified element.

If the application needs to load more than one script, the calls to loadScript() can be nested, like this:

// loadApp() - Function to load the application after ensuring Angular is ready
function loadApp(myAngular, scriptPath, elementToBind)
{
    loadScript(scriptPath +'mainController.js', function () {
        loadScript(scriptPath + 'surveyService.js', function () {
            loadScript(scriptPath + 'settingsController.js', function () {
                loadScript(scriptPath + 'spDataService.js', function () {
                    // Bind the main controller
                    myAngular.bootstrap(elementToBind, ['microSurvey']);
                });
            });
        });
    });
}
Lines 29-59: In-line Script

This code runs in-line – that is, it runs while the script is being loaded.

Lines 30-34 find the DOM element to be bound. The rule is that Angular will bind to the parent of the initUI.js script element. Since this code runs in-line, the DOM is still loading when it runs. In particular, the browser is loading the <script> element. Therefore we know that the <script> element will be the last one in the DOM tree, so the code begins at the top node (document.documentElement) and iterates until it comes to the last element, which will be the <script> tag. The parent of that element is the one to bind to. An answer on Stack Overflow provided this clever riff.

Lines 38-45 check to see if an old version of Angular is on the page. If so, it’s saved into a temporary variable and then restored later on, in lines 53-55. If an acceptable version of Angular is found, it can be reused to save time and browser memory. If not, then Angular is loaded using loadScript() in line 50. Notice that the code could be modified slightly to remove Angular from the page and leave literally no trace in the global namespace. I decided to leave either the old angular or the one I load, so as to avoid extra copies running if it’s not really necessary.

Once the right version of Angular is in hand, loadApp() is called, passing in the angular object and the element to be bound.

All this serves to allow many Angular apps to share the page. I hope this pattern is helpful and that all of our web parts and widgets play well together!


As chance would have it, just a couple days ago Kimiko Ishizaka published a masterful performance of Bach’s Well-Tempered Clavier in celebration of his 330th birthday. I enjoyed listening to it as I was writing this article, and highly recommend it. Be sure to check out her Open Well-Tempered Clavier site to listen and learn more about this musical masterpiece.

One thought on “The Well-Tempered AngularJS Web Part

  1. Bob:

    I really enjoyed listening to Bach while reading this. Your analogy is superb.

    I’ve been using LABjs, RequireJS, and some of my own fresh-baked jQuery to bootstrap my applications for a while now.

    Note that I said ‘application’, not ‘app’. This whole thing can happen without the app model and in any version of SharePoint.

    Harmonious code is good code.

    M.

    Liked by 1 person

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 )

Facebook photo

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

Connecting to %s