The primary reason for the influx of interest in JavaScript MVC frameworks is improving separation of concerns between HTML and JS. I’m going to demonstrate why decoupling of HTML and JS is so important and how AngularJS helps solve the problem.
Why decoupling is so important
Traditional DOM selection, traversal, and manipulation is the root of tightly coupled HTML and JS—and the bane of a front-end developer’s existence. It’s not that it is difficult to write or understand, but rather that it is exceedingly difficult to maintain. Even the most seemingly innocuous JS containing node names, CSS classes, or event bindings causes a tightly coupled architecture to quickly emerge. Consider a simple jQuery app that lets the user arbitrarily add items to a list:
I took it a step further than many jQuery apps in the wild by introducing a rudimentary template for the list item rather than compositing the item entirely in JS. However, the app still naturally suffers from some serious coupling. Here are a few realistic scenarios the app might face in the future:
- Multiple vehicle entry methods needed.
- Will need to change element selection by ID to selection by class.
- Will need to adjust DOM selection in JS.
- What is conceptually a change only to HTML clearly will also require changes to JS.
- List item template changes.
- Will need to change template DOM selection and manipulation in JS.
- Same as above; conceptually HTML-only, actually not.
- Multiple displays of garage needed.
- Similar problems as #1 and #2.
The code changes required to implement these features are significant and involve disparate contexts and thus are costly and prone to error. Here is a simple diagram to visualize the coupling:
Separation of concerns
Surely there must be a better way. Consider how the diagram above might change if we introduce MVC-inspired separation of concerns:
Enter AngularJS
Here’s the same app using basic AngularJS instead of jQuery. (If you’re keen on UX, you’ll notice I left out a few usability features from the previous example. We’ll add those back momentarily using more advanced AngularJS techniques.)
The DOM selection, traversal, and manipulation disappear! What you see is effectively the diagram of the three circles—the vehicle data is now abstract and the DOM and JS coupling is virtually eliminated. The three “what ifs” mentioned before which are conceptually HTML-only changes now actually are HTML-only changes since the underlying data model doesn’t change (and the JS is only referencing the abstract data model).
If I add only a little bit of HTML on the view layer, I get cool new features that operate on the same data set—no changes to JS required (conceptual view-only change now a reality):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<p ng-show="!vehicles.length"> Your garage looks a little empty. Do you want <a ng-click="addVehicle({name: 'Ferarri', type: Vehicle.TYPE_CAR})">a Ferrari</a>? </p> <p ng-show="vehicles.length">You have {{vehicles.length}} vehicle<span ng-show="vehicles.length != 1">s</span> in your garage.</p> <form ng-submit="addVehicle({name: addCarName, type: Vehicle.TYPE_CAR}); addCarName = ''"> <label for="addCarName">Add a car:</label> <input type="text" ng-model="addCarName" placeholder="Car name" /> <button type="submit">OK</button> </form> <form ng-submit="addVehicle({name: addMotoName, type: Vehicle.TYPE_MOTORCYCLE}); addMotoName = ''"> <label for="addMotoName">Add a motorcycle:</label> <input type="text" ng-model="addMotoName" placeholder="Motorcycle name" /> <button type="submit">OK</button> </form> |
Usability sugar as promised
So you may have noticed a few UX niceties in the jQuery version of the app that we lost later on:
- Vehicle name input focused after changing vehicle type.
- Vehicle name input focused after clicking submit button with empty name and getting “name required” error.
- Vehicle name input focused after clicking submit button and successfully adding a vehicle.
Doing this in AngularJS is to some extent another topic—still along the lines of separation of concerns, but not exactly MVC—very specific to the problem domain of the DOM. The way AngularJS solved this particular problem is clever, unique among JS frameworks, and extremely robust; the solution is in what are called directives, “a way to teach HTML new tricks.” What I am doing here is essentially inventing a couple new HTML element attributes that will abstract out the concept that is focusing an input on various cues:
Note the new directives focusAfterChange and focusIf defined in the JS along with associated usage in HTML as <input ...="" focus-after-change="..." focus-if="..."/>, which elegantly encapsulates the behavior and still manages to abstract away DOM selection, etc.
Comments or questions are always welcome!