So I've now built my first real application using AngularJS. It's a fun side-project which my wife and I use to track what we spend money on. It's not a work project but it's also not another Todo list application. In fact, the application existed before as a typical jQuery app. So, I knew exactly what I needed to build but this time trying to avoid jQuery as much as I possibly could.
The first jQuery based version is here and although I'm hesitant to share this beginner-creation here's the AngularJS version
The following lists were some stumbling block and other things that stumped me. Hopefully by making this list it might help others who are also new to AngularJS and perhaps the Gods of AngularJS can see what confuses beginners like me.
1. AJAX doesn't work like jQuery
Similar to Backbone, I think, the default thing is to send the GET, POST request with the data the body blob. jQuery, by default, sends it as application/x-www-form-urlencoded
. I like that because that's how most of my back ends work (e.g. request.GET.get('variable')
in Django). I ended up pasting in this (code below) to get back what I'm familiar with:
module.config(function ($httpProvider) {
$httpProvider.defaults.transformRequest = function(data){
if (data === undefined) {
return data;
}
return $.param(data);
};
$httpProvider.defaults.headers.post['Content-Type'] = ''
+ 'application/x-www-form-urlencoded; charset=UTF-8';
});
2. App/Module configuration confuses me
The whole concept of needing to define the code as an app or a module confused me. I think it all starts to make sense now. Basically, you don't need to think about "modules" until you start to split distinct things into separate files. To get started, you don't need it. At least not for simple applications that just have one chunk of business logic code.
Also, it's confusing why the the name of the app is important and why I even need a name.
3. How to do basic .show()
and .hide()
is handled by the data
In jQuery, you control the visibility of elements by working with the element based on data. In AngularJS you control the visibility by tying it to the data and then manipulate the data. It's hard to put your finger on it but I'm so used to looking at the data and then decide on elements' visibility. This is not an uncommon pattern in a jQuery app:
<p class="bench-press-question">
<label>How much can you bench press?</label>
<input name="bench_press_max">
</p>
if (data.user_info.gender == 'male') {
$('.bench-press-question input').val(response.user_info.bench_press_max);
$('.bench-press-question').show();
}
In AngularJS that would instead look something like this:
<p ng-show="male">
<label>How much can you bench press?</label>
<input name="bench_press_max" ng-model="bench_press_max">
</p>
if (data.user_info.gender == 'male') {
$scope.male = true;
$scope.bench_press_max = data.user_info.bench_press_max;
}
I know this can probably be expressed in some smarter way but what made me uneasy is that I mix stuff into the data to do visual things.
4. How do I use controllers that "manage" the whole page?
I like the ng-controller="MyController"
thing because it makes it obvious where your "working environment" is as opposed to working with the whole document
but what do I do if I need to tie data to lots of several places of the document
?
To remedy this for myself I created a controller that manages, basically, the whole body. If I don't, I can't manage scope data that is scattered across totally different sections of the page.
I know it's a weak excuse but the code I ended up with has one massive controller for everything on the page. That can't be right.
5. setTimeout()
doesn't quite work as you'd expect
If you do this in AngularJS it won't update as you'd expect.
<p class="status-message" ng-show="message">{{ message }}</p>
$scope.message = 'Changes saved!';
setTimout(function() {
$scope.message = null;
}, 5 * 1000);
What you have to do, once you know it, is this:
function MyController($scope, $timeout) {
...
$scope.message = 'Changes saved!';
$timeout(function() {
$scope.message = null;
}, 5 * 1000);
}
It's not too bad but I couldn't see this until I had Googled some Stackoverflow questions.
6. Autocompleted password fields don't update the scope
Due to this bug when someone fills in a username and password form using autocomplete the password field isn't updating its data.
Let me explain; you have a username and password form. The user types in her username and her browser automatically now also fills in the password field and she's ready to submit. This simply does not work in AngularJS yet. So, if you have this code...:
<form>
<input name="username" ng-model="username" placeholder="Username">
<input type="password" name="password" ng-model="password" placeholder="Password">
<a class="button button-block" ng-click="submit()">Submit</a>
</form>
$scope.signin_submit = function() {
$http.post('/signin', {username: $scope.username, password: $scope.password})
.success(function(data) {
console.log('Signed in!');
};
return false;
};
It simply doesn't work! I'll leave it to the reader to explore what available jQuery-helped hacks you can use.
7. Events for selection in a <select>
tag is weird
This is one of those cases where readers might laugh at me but I just couldn't see how else to do it.
First, let me show you how I'd do it in jQuery:
$('select[name="choice"]').change(function() {
if ($(this).val() == 'other') {
}
});
Here's how I solved it in AngularJS:
$scope.$watch('choice', function(value) {
if (value == 'other') {
}
});
What's also strange is that there's nothing in the API documentation about $watch
.
8. Controllers "dependency" injection is, by default, dependent on the controller's arguments
To have access to modules like $http
and $timeout
for example, in a controller, you put them in as arguments like this:
function MyController($scope, $http, $timeout) {
...
It means that it's going to work equally if you do:
function MyController($scope, $timeout, $http) {
...
That's fine. Sort of. Except that this breaks minification so you have to do it this way:
var MyController = ['$scope', '$http', '$timeout', function($scope, $http, $timeout) {
...
Ugly! The first form depends on the interpreter inspecting the names of the arguments. The second form depends on the modules as strings.
The more correct way to do it is using the $inject
. Like this:
MyController.$inject = ['$scope', '$http', '$timeout'];
function MyController($scope, $http, $timeout) {
...
Still ugly because it depends on them being strings. But why isn't this the one and only way to do it in the documentation? These days, no application is worth its salt if it isn't minify'able.
9. Is it "angular" or "angularjs"?
Googling and referring to it "angularjs" seems to yield better results.
This isn't a technical thing but rather something that's still in my head as I'm learning my way around.
In conclusion
I'm eager to write another blog post about how fun it has been to play with AngularJS. It's a fresh new way of doing things.
AngularJS code reminds me of the olden days when the HTML no longer looks like HTML but instead some document that contains half of the business logic spread all over the place. I think I haven't fully grasped this new way of doing things.
From hopping around example code and documentation I've seen some outrageously complicated HTML which I'm used to doing in Javascript instead. I appreciate that the HTML is after all part of the visual presentation and not the data handling but it still stumps me every time I see that what used to be one piece of functionality is now spread across two places (in the javascript controller and in the HTML directive).
I'm not givin up on AngularJS but I'll need to get a lot more comfortable with it before I use it in more serious applications.