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.