The Ajax Aspect
Ajax is important to much of what I do in my projects. Headers and JSON get sent back and forth a lot. Also, there are plenty of callbacks for dealing with stuff from the server.
So how do Angular and jQuery compare in that department? To be honest, you don't lose much. You go without Angular's $http service but you still have jQuery's ajax function to work with. Here's what a json-in-json-out scenario looks like in AngularJS.
Here's the same thing but done with jQuery.
Overall, the only significant feature here is that $http has to be dependency injected. I don't know that I gain anything from grabbing a service from out of whatever container it happens to comes from. It feels vaguely like working with a Spring container in an enterprise Java project. But that's about it for me.
So how do Angular and jQuery compare in that department? To be honest, you don't lose much. You go without Angular's $http service but you still have jQuery's ajax function to work with. Here's what a json-in-json-out scenario looks like in AngularJS.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var controller = function($http){ | |
var json_args = { | |
num1: 5, | |
num2: 3 | |
}; | |
var showResult = function(res){ | |
alert(res.data.sum); | |
} | |
var req = { | |
url: "/addnums", | |
method: "post", | |
headers: { | |
"Content-type": "application/json" | |
}, | |
data: json_args | |
}; | |
var promise = $http(req); | |
promise.then(showResult); | |
}; |
Here's the same thing but done with jQuery.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$(function(){ | |
var json_args = { | |
num1: 5, | |
num2: 3 | |
}; | |
var showResult = function(res){ | |
alert(res.sum); | |
}; | |
var req = { | |
url: "/addnums", | |
method: "post", | |
headers: { | |
"Content-type": "application/json" | |
}, | |
data: JSON.stringify(json_args) | |
}; | |
var promise = $.ajax(req) | |
promise.then(showResult); | |
}); |
Know Thy DOM
Giving up Angular did force me to think more about the DOM. Vanilla jQuery doesn't insert itself as readily into HTML code as an Angular directive can. That fact made direct DOM dealings more important. For examples, here's how you generate bullet point list in Angular.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<title>AngularJS looping example</title> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script> | |
</head> | |
<body> | |
<div ng-app="app" ng-controller="controller"> | |
<ul> | |
<li ng-repeat="word in words">{{word}}</li> | |
</ul> | |
</div> | |
<script> | |
var controller = function($scope){ | |
$scope.words = "This is my list of words".split(" "); | |
}; | |
angular.module("app", []) | |
.controller("controller", controller); | |
</script> | |
</body> | |
</html> |
Doing the same thing in jQuery, you get something that looks like this.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<title>jQuery Loop Concat</title> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script> | |
</head> | |
<body> | |
<ul id="targetlist"> | |
</ul> | |
<script> | |
$(function(){ | |
var data = { | |
myinfo: "Some words to comes up with here".split(" ") | |
}; | |
data.myinfo.forEach(function(word){ | |
var listItemText = "<li>" + word + "</li>"; | |
var listItemDom = $(listItemText); | |
$("#targetlist").append(listItemDom); | |
}); | |
}); | |
</script> | |
</body> | |
</html> |
Filling The Template Void with Handlebars.js
The good news is that living without Angular doesn't mean giving up on client-side templates entirely. It turns out that Handlebars.js fills that void nicely. Drop a template between script tags. Load it up. Insert the data. All is well.
Yes, there is DOM dirty work to do but that's okay. At least there isn't that error-prone process of piecing strings together so much. That's actually pretty nice.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<title>Handlebars looping example</title> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script> | |
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.min.js"></script> | |
</head> | |
<body> | |
<div id="targetArea"></div> | |
<script id="listTemplate" type="text/x-handlebars-template"> | |
<ul> | |
{{#each wordList}} | |
<li>{{this}}</li> | |
{{/each}} | |
</ul> | |
</script> | |
<script> | |
$(function(){ | |
var data = { wordList: "This is my list of words".split(" ") }; | |
var templateText = $("#listTemplate").html(); | |
var template = Handlebars.compile(templateText); | |
var renderedText = template(data); | |
var renderedDom = $(renderedText); | |
$("#targetArea").append(renderedDom); | |
}); | |
</script> | |
</body> | |
</html> |
My Dynamic Button Trip Up
One area that tripped me up with Handlebars was button handling. Buttons for deleting and changing notes are specific to each note. The event handlers for those buttons need to reflect that.
Handlebars is really good at taking data text and plugging it into template text. Where it gets weird is when events come into the picture.
The thing is this. You generally don't attach event handlers directly to the text that Handlebars generates. You attach them to some kind of DOM object like this...
Okay, so what to do?
Here was my solution. Let Handlebars do the whole template thing. Have jQuery wrap the text up as a DOM element. Then, just let jQuery attach event handlers to that. Here is what that code ended up looking like.
Handlebars is really good at taking data text and plugging it into template text. Where it gets weird is when events come into the picture.
The thing is this. You generally don't attach event handlers directly to the text that Handlebars generates. You attach them to some kind of DOM object like this...
$("#myButton").click(function(e){})
Here was my solution. Let Handlebars do the whole template thing. Have jQuery wrap the text up as a DOM element. Then, just let jQuery attach event handlers to that. Here is what that code ended up looking like.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var deleteButton = makeDeleteButton(rec.id); | |
var changeButton = makeChangeButton(rec.id, rec.title, rec.detail); | |
var rowData = { | |
id: rec.id, | |
title: rec.title, | |
detail: rec.detail | |
}; | |
var rowTemplText = $("#notesRowTempl").html(); | |
var rowTemplate = Handlebars.compile(rowTemplText); | |
var renderedRowText = rowTemplate(rowData); | |
var renderedRowDom = $(renderedRowText); | |
renderedRowDom.find(".deleteButtonCell").append(deleteButton); | |
renderedRowDom.find(".changeButtonCell").append(changeButton); | |
// notesTable.append(row); | |
notesTable.append(renderedRowDom); |
So that gets the job done. Once the DOM is created, we can attach a DOM button to that structure. And that's it. In case you're curious, you can find the full js file where this is set up over here. And if I'm "doing it wrong", feel free to yell at me in the comments section.
Final Thoughts
Going without Angular was nowhere near as hard as I thought it would be. The Ajax parts are reasonable thanks to the core jQuery libraries. The DOM aspects I found myself needing to handle really weren't that burdensome. And thanks to Handlebars, I didn't even have to give up templates.
What Angular is good for is catering to my enterprise and Java background. I've done my share of mixing logic and HTML together from my experiences in JSP and ColdFusion. The dependency injection that happens in Angular feels like a simplified version of what happens in a lot of Spring projects.
All being said, I don't think I'm going to disavow AngularJS entirely because I'm still not convinced that it's a "bad" framework. For bigger web applications, it might well be ideal for all I know. It's just that I now know that the set of problems Angular fits for isn't as big as I thought. And that's an okay thing to come to terms with. Here's to the programmer's journey!
What Angular is good for is catering to my enterprise and Java background. I've done my share of mixing logic and HTML together from my experiences in JSP and ColdFusion. The dependency injection that happens in Angular feels like a simplified version of what happens in a lot of Spring projects.
All being said, I don't think I'm going to disavow AngularJS entirely because I'm still not convinced that it's a "bad" framework. For bigger web applications, it might well be ideal for all I know. It's just that I now know that the set of problems Angular fits for isn't as big as I thought. And that's an okay thing to come to terms with. Here's to the programmer's journey!