Monday, June 16, 2014

Salvaging Legacy Apps with AngularJS UI Components

Below is a useful excerpt from Chapter 7 of my upcoming book for those with jQuery/Backbone/Require.js app looking to upgrade to AngularJS:

Salvaging Legacy Apps with AngularJS UI Components


We'll finish this chapter by taking a bit of a detour from the topic of building and delivering our UI component library, and discuss a unique and very useful thing we can do with the UI components that we create.

If you are reading this book, it is very likely that you are one of two types of engineer.  The first type are those who are fortunate to have the luxury of being able to build new applications using the latest front-end technologies.  The second type are those who have the burden of maintaining legacy front-end applications that suffer from multiple revolving developers doing their own thing, bloated and repeated functionality everywhere, and performance so bad that loading and user responsiveness can be measured in seconds rather than milliseconds.  This section is for the second type. 

In the first few chapters, we extoled the benefits of AngularJS compared to older frameworks, especially in regards to drastically reducing binding boilerplate and other repeated code.  Bloated applications full of jQuery spaghetti are the direct result of poor planning, short term focus, and the dysfunctional layers of management found in most enterprise organization of significant size and age.  Marketing driven organizations often suffer from the misguided belief that you can produce a "Cloud" product faster by throwing scores of offshore contractors at the project.  A year later, the web based product might be functional, but it far from maintainable given all the corners that were cut to push the product out the door including testing, documentation, and other best practices.  Within two years, development grinds to a halt due to bloat and the inability to fix a bug without causing three more in the process.

In certain cases, it may be possible to salvage the application from the inside out by systematically replacing repeated functionality and code with common, reusable UI components built with AngularJS.  The assumption is that the page is under the control of a legacy framework, but if the application meets certain conditions it is salvageable including:

  • The app allows the developer control over lifecycle events resulting in DOM redraws
  • DOM redraws are template based, not JavaScript based
  • AngularJS core and UI component modules can be loaded with some control over order
  • Any jQuery in the page is a relatively recent version
  • The root element(s) that would contain the components are accessible


Existing frameworks that are highly configuration based (i.e. Extjs) are likely beyond repair because most of the DOM rendering is scattered deep in layers of its core JavaScript which is not accessible without modifying the framework.  Frameworks that are jQuery based (imperative), on the other hand, make excellent salvage candidates including Backbone.js.

Our example will include code snippets that illustrate how simple it is to embed an AngularJS UI component in a Backbone.js / Require.js application.  The first task is to make sure that AngularJS core is loaded and angular is available as a global.  If loading AngularJS core will be done via AMD with Require.js, it should be done early in the file used by Require.js to bootstrap all of the modules.  If jQuery is loaded here, AngularJS must be after jQuery if it is a dependency.  Likewise, the code containing the AngularJS modules must load after AngularJS core.  Loading these files asynchronously without a dependency loader that guarantees order will not work.

7.10 Embedding an AngularJS UI Component in Require/Backbone

// main.js
require([
    'underscore',
    'jquery',
    'backbone',
    'angular',
    'angularComponents',
    '...'
    ], function( mods ){
       ...
});

//////////////////////////////
<!-- template_for_view_below.hbs.html -->
...
<div id="attach_angular_here">
    <uic-dropdown ...></uic-dropdown>
</div>
...

//////////////////////////////
// view.js
define([
    'deps'
], function (deps) {
    return View.extend({
        className: 'name',
        initialize: function () {
            ...
        },
        render: function () {
            // give the parent view time to attach $el to the DOM
            _.defer(function () {
                try{
                    // this replaces <div ngApp=" uicDropdown "
                    angular.bootstrap('#attach_angular_here', ["uicDropdown"]);
                }catch(e){
                    console.error('there is a problem');
                }
            });
        });
    }
});


The pseudo code above demonstrates how and where to place the AngularJS code within the lifecycle of the Backbone.js application.  The important pieces include making sure the dependencies get loaded in the correct order and that the DOM element that contains the custom element and where we will bootstrap AngularJS is appended to the DOM before angular.bootstrap() is executed.  Using lodash defer or setTimeout places the contained code at the end of the JavaScript execution stack.  For robustness, the try block is included so any problems can fail gracefully without halting the UI.
AngularJS apps can be bootstrapped imperatively on multiple DOM elements assuming they are not part of the same hierarchy.  For best performance, however, the number of AngularJS app instantiations should be kept to a minimum by attaching to elements higher in the DOM hierarchy if possible.  The pattern above can be used to replace any of possibly several different dropdowns implementations throughout the application.  In the process, lines of source code will be reduced and performance will increase.


This is one scenario of injecting AngularJS into and existing application.  In practice, it make take some creativity and a few attempts to find a system that works for your particular application.  However the time spent can be well worth it given that the alternative would be to start from scratch and develop an entirely new application while having to maintain the old one until it is complete.