There are days in a life that emerge as stand outs amongst the rest. Some of those days are expected; a wedding perhaps or the birth of a child. Others happen without fanfare. The day I found Angular was one of those days.
I was in the midst of working on the Arch LMS. It had become laborious. The UI I had envisioned was becoming a mess of ajax calls glued together with jQuery. It worked, but it was fragile. That wasn't really jQuery's fault. I used it for something it wasn't meant to do and tracking all those events was impossible.
Seeking something else, I found myself watching a presentation on Knockout. Not bad. It did lead me to Underscore.js, but for some reason it wasn't clicking for me. Perhaps if I'd given it more time. Still, I thought there had to be a better way. Then I found Angular.
Maybe it was because of my background in WebObjects, but the idea of creating directives (components in wo-speak) on the client side was exciting. And all those directives need to work, was a bit of json. Lovely. After 16 years, Web development could be fun again.
I wanted to scrap the whole application and start from scratch, but quickly realized I couldn't. That would take too long and there was too much to do. Instead, I had to find a way to begin incorporating Angular immediately, while keeping my existing codebase intact.
My existing application utilized a lot of document
level events that I could trigger from any page with click
handlers. I simply passed the required options through those. For example:
// main script
$(document).on('edit-plan', function(e, options){
// edit the lesson plan with options.id
});
// somewhere else, perhaps the lesson plans page
$('.edit-plan').click(function(e){
e.preventDefault();
$(document).trigger('edit-plan', {
id: $(this).data('id')
});
});
All I needed to do to begin using Angular was tap into the edit-plan
event. Already using RequireJS, I wrote a function that allowed me to inject a module into the dom and bootstrap it. Here is the function:
/**
* xg-bootstrap.js
*
* Injects an Angular module into the DOM asynchronously and returns a promise
* to resolve when the injection is complete.
*/
define(['angular'], function(angular) {
return function(app, appEl, $selector){
var d;
angular.injector(['ng']).invoke(['$q', function($q){
d = $q.defer();
angular.element($selector || 'body').append(appEl);
angular.bootstrap(appEl, [app.name]);
d.resolve({
'app': app,
'appEl': appEl,
'scope': appEl.scope()
});
}]);
return d.promise;
};
});
I could then create a module or mini-app unencumbered by any other dependencies and inject it like this:
// mods/alert/app.js
define(['angular', 'libs/xg-bootstrap', 'text!mods/alert/template.html'], function(angular, xgBootstrap, template){
return function module(){
var appEl = angular.element(template);
var app = angular.module('AlertApp', []);
app.controller('AlertAppCtrl', ['$scope', '$sce', function($scope, $sce){
$scope.message = "";
$scope.close = function(){
appEl.remove();
};
$scope.$watch('message', function(){
$scope.messageHtml = $sce.trustAsHtml($scope.message);
});
}]);
return xgBootstrap(app, appEl);
};
});
<div class="xg-modal">
<div class="xg-alert"><button ng-click="close()">Close</button></div>
</div>
And to invoke the app:
// main.js
$(document).on('alert', function(event, options){
requirejs(["mods/alert/app"], function(module){
var promise = new module();
promise.then(function(app){
app.scope.$apply(function(){
app.scope.message = options.message;
});
});
});
});
// within some html page
$('#alert-button').click(function(event){
event.preventDefault();
$(document).trigger('alert', {
message: "À tout le monde, hello."
});
});
So that's what I did, one by one replacing my jQuery-based dialogs and code with Angular based ones. I was able to immediately use Angular, while moving forward with the application. And it was fun.