We were thrilled with the results of integrating Backbone.js into our toolset, which gave much needed structure to our DOM-bound, jQuery spaghetti. As an unopinionated library, Backbone.js let us gradually phase in components without having to fully refactor existing code. Models abstracted us away from boilerplate AJAX mechanics, and the event system helped decouple our AJAX and DOM-specific logic and destroyed many-a-pyramid-of-doom.
The result of our Backbone.js experiment was a durable web application architecture that ended up supporting three generations of underlying authoring engines with minimal front-end changes. Additionally, our company used Backbone.js to build a variety of internal tooling for everything from billing to infrastructure monitoring.
In the Fall of 2013, one of our UX engineers ported a side project from Backbone.js to AngularJS in order to kick the tires of a budding framework. The side project was an HTML5 webapp deployed to Android and iOS using the Phonegap toolkit. It contained several layers of navigation, forms, and a user system so it was simple enough for an overhaul, but complex enough to test a variety of AngularJS features.
He was immediately struck by many of the things AngularJS boasts of on its homepage: a sharp reduction in boilerplate code, 2-way data bindings, and lack of direct DOM manipulation. The views were built first — top-to-bottom — then the controllers, services, and router. Everything was modularized, but not too sharded. The previous Backbone.js version contained a dozen tiny Underscore template files and several `Backbone.View` classes just to render a profile detail page. With AngularJS, there was one view and one controller so the code was more centralized and readable. Writing the app “template-first” better mapped to way he approached web development.
With the mobile app successfully ported in short order, our experimenter brought the lab to the office and built the new Stylebooker app in AngularJS. This app applies common English grammar rules and punctuation to our generated stories, similar to how a writer uses the AP Stylebook while editing an essay. Other UX engineers became more familiar with AngularJS with each passing code review on Github.
With the new AngularJS app built, demoed, and discussed, we decided to provide limited support for AngularJS apps moving forward. The next step was to figure out how to manage an AngularJS app’s lifecycle cleanly within a predominantly Backbone.js environment. Our experience with this integration would determine if we had the ability to broaden the scope of our AngularJS experiment.
The main Backbone.js app consisted of nested application and view structures that used a global router for navigation. When the user navigated to the URL for the new AngularJS app, the main app rendered a container element, then bootstrapped the AngularJS app into the fresh DOM container.
When we finished attaching all our apps to the global router, we came away with a stable, albeit muddled architecture:
After nailing down the Backbone.js-to-AngularJS adapter and testing the integration over the course of a few weeks, we started building more AngularJS apps for our new Quill features. To make a new bootstrapped AngularJS app, an engineer simply needed to copy the adapter from an existing app, update the route definitions, and rename a few variables.
As we built more and more of our application with AngularJS, we came to appreciate its testability. Controllers and services were much easier to inject into test suites and instantiate with mock data in isolation compared with Backbone.js, mostly because of AngularJS’ reliance on dependency injection.
Thanks to AngularJS’ modularity and clear separation of concerns, other engineers without a web development background were able to start contributing. They didn’t need to know jQuery inside-out or dredge through multiple view classes and partials to logically piece together the purpose of an app.
Our team increased its adoption of AngularJS and the codebase soon reached a tipping point; AngularJS powered more apps than Backbone.js. However, we were still using Backbone.js as the application glue. Soon thereafter, we flipped the control by refactoring our Pipeline page to use a unified AngularJS module to power the rest of the apps.
The new master module housed a single router that consolidated all of the routing definitions from its children. Now that the routing mechanism was in AngularJS’ hands, we were able to remove all the Backbone.js-to-AngularJS adapters in the system, and instead supported our older Backbone.js apps with a new AngularJS-to-Backbone.js directive-based adapter.
The landscape of front-end engineering is constantly in flux, arguably more so than any other software discipline. New languages, frameworks, transport protocols, browser capabilities, and build tools are released every week, and keeping up with them — let alone evaluating and adopting them into an existing codebase — can be a challenge.
Making the leap from Backbone.js to AngularJS forced us to face these challenges head-on and learn a lot about the software lifecycle. It highlighted the benefits of doing an organic, iterative transition. We built temporary adapters that allowed the two frameworks to coexist, and gradually phased them out as we refactored our application containers. This allowed us to avoid rewriting the entire codebase at once, and consequently avoid having to halt all new feature development. Thanks to AngularJS our development velocity has improved and our codebase is more accessible to all engineers, particularly those unfamiliar with jQuery and the DOM.
Looking toward the future, we know new technologies will arise to challenge the status quo and force us to reevaluate our design decisions. When that time comes, we’ll apply the same practices of research, experimentation, and gradual rollout that served us so well this year.