AngularJS – Weekly Tech Learning

This post is part of my weekly tech learning series, where I take a few hours each week to try out a piece of technology that I’d like to learn.

After last week’s experience with riot.js I decided to experiment with another JavaScript library/framework I’ve been hearing a lot about, AngularJS.

Angular bills itself as a way to extend HTML for dynamic views in web applications. This sounded a bit strange but after seeing how Angular works and embeds itself into HTML, it makes sense.

Documentation

The first place I start with anything new is the documentation. I’m the kind of person who reads the manual for a new camera before using it.

Angular’s documentation really shines. The homepage has videos, several example apps, and there are a ton of resources deeper into the site.

What was really surprising is that Angular exceptions have a link on the JavaScript console that point to the angular website with details about what happened. This made debugging much easier because I could take my specific exception and find out what it meant in the more general sense.

Todo list application with AngularJS

Like my other applications, I built a todo list application with Angular. I’ve done enough of them that I know the scope so I can focus learning how Angular works.

Surprising, one of the demo applications on Angular’s homepage was a simple todo application. If you compare it to mine, you’ll notice I used much of the same code. But I still built my by hand so I can see how it works step-by-step.

Features

Being a todo list, I kept the feature-set short.

  1. Single page app without refreshes
  2. List all open todo items
  3. Add a new todo item
  4. Complete a todo item
  5. Delete a todo item

Since the demo application did most of this, I had extra time to add a basic localStorage persistence feature.

HTML

The first thing I noticed with Angular is that much of its view is put into the HTML. You decorate various elements and sections with ng values to connect them to Angular. This was a bit shocking to me, since years ago there were movements to get JavaScript out of the HTML (unobtrusive JavaScript, progressive enhancement). To see non-standard HTML used by Angular felt odd, like a step backwards.

But if you go back to what Angular’s goals, it makes sense. It wants to extend HTML, which means it will be changing what I’d consider HTML.

The first hiccup I had was getting my controller to connect. I had my ng-app and ng-controller declared, Angular was loading, and everything looked right. But my controller kept appearing as undefined. After some debugging, I found I needed to use ng-app differently than the demo code. I needed to fully declare my app (ng-app vs ng-app='tech-learning').

Once that was in place, things started working like they were supposed to.

Data binding

Like knockout.js, AngularJS puts some control flow and loops directly into the HTML. In my case I needed that for listing each todo item with a standard template (ng-repeat). In that loop, {{todo}} was used to reference the current todo item, just like most templates.

Completing a todo

The interesting thing with the demo application is that they used only the data binding to mark a todo as complete or not. The checkbox has a ng-model="todo.done" which toggles the done value of a todo object. This happens automatically and it also changes the css class for the todo text. With some css, that means it automatically gets a strike-through when it’s completed, right from the checkbox.

This did cause a problem with my localStorage though. Because the view was accessing the model directly, I couldn’t call the function to persist the data. I’m sure if I had more time I could hook up the persistence a bit more transparently so ng-model would automatically save.

Controller (and Model, kinda)

With the HTML page acting as the View, a JavaScript file functions as the Controller. What was odd, coming from a Rails background, is that the Controller also embedded the Model. The Model wasn’t explicitly declared but you could see it.

Maybe this was because a todo list application is simple so it doesn’t need a separate Model. But it still seemed like keeping them together would be a bit of a pain to test and maintain over time.

Much of Angular’s View-Controller connection is done through the scope, or $scope in the code. This seems to be a shared object that the view can access as well as the controller where it’s passed in as a parameter.

Data and behavior defined on this scope object let the View call it. In fact, I guess this ease of callability is what helps Angular be easy to develop with.

load()

The load() function was an addition that I added to support localStorage. Basically it checks localStorage to see if there are any todo items there and returns them.

This works in conjunction with the todos variable which is where the controller tracks the list of todo items (and which the View uses to build its list).

add()

This function has three behaviors:

  1. Add a new todo item based on the form submission
  2. Clear the todo field name
  3. Persist the data to localStorage

The View is hooked up to this through the ng-submit="add()" declaration on the form. This works like a binding that takes the submit events and sends it to the add() function. The name field is also given a ng-model declaration so the controller can access it (e.g. getting the value, clearing).

deleteTodo()

I also wanted to be able to delete a todo item. The deleteTodo() function does this by searching for the todo item and removing it from the todos array.

The interesting thing was that I hooked it up using the ng-click and was able to pass in the current todo from the View. This meant I didn’t have to bother with ids or some unique key to search on.

persist()

The persist() function is just a simple little wrapper for saving the current todos into localStorage.

localStorage compatibility

Based on my experience with localStorage in the past, I’ve created a simple compatibility object for browsers who don’t support localStorage. All it does it mockup the API I need with no-op functions.

(In a larger application, this isn’t the best approach. Feature detection will notice this object and think that a browser actually does support localStorage. So watch out if you try this.)

Final thoughts

That’s it. All together, my AngularJS todo application is around 30 lines of JavaScript with a dozen or so of HTML. It’s one of the smaller ones I’ve built, which isn’t surprising since Angular is supposed to be a higher-level library than others.

Overall though, I wasn’t happy with a few things in AngularJS.

Code in HTML

I strongly dislike putting that much “code” into my HTML. I’ve seen too many teams start with a little bit of code and then they end up with a mess that is impossible to work in. (knockout.js also suffers from this.

With a good team, the right practices, and an eye for problems this could be worked around. But it’s easy to fall off.

(Granted the alternatives aren’t much better: building HTML strings in JavaScript is horrible and JavaScript templates can become a pain too. It’s not an easy problem.)

One plus with AngularJS from what I understand is that you can scope it to a specific part of the HTML document. So instead of one ball of mess, you might have several smaller Angular controllers.

(I also seems like SEO can be a bit of an issue with angular. Some people say angular content is invisible because it’s JavaScript, others say that Google will run angular code and see the content. All I know is that you’ll have to do your own tests if you have a public angular site.)

Large API

AngularJS’s API is large. Even if you’re just looking at the core, there’s a lot there. It seems like it also has its own API for events, which could be interesting if you use jQuery also.

As part of my advancing age, I’m starting to prefer libraries and frameworks with a smaller footprint. Not just in size but also in how many methods/functions they expose. Having less of an API to remember means more of my application can fit in my head.

It was good to see Angular has separated some components out of the core, which reduces both its size and API footprint.

Summary

I can see using AngularJS with some applications. I’m not putting it into my general purpose toolkit quite yet, but I’d feel comfortable working with it in an existing codebase.

I also hear the 2.0 version is going to have some significant changes which improve some of the problem areas.

Code

Basic HTML page with the skeleton structure, ng declarations, and the todo list.

<!-- www/index.html -->
<!DOCTYPE html>
<!--[if lt IE 7]>      <html ng-app='tech-learning'  class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>         <html ng-app='tech-learning'  class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>         <html ng-app='tech-learning'  class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html ng-app='tech-learning' class="no-js"> <!--<![endif]-->
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <title></title>
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width">
 
        <!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
 
        <link rel="stylesheet" href="css/normalize.css">
        <link rel="stylesheet" href="css/main.css">
        <link rel="stylesheet" href="css/app.css">
    </head>
    <body ng-controller="TodoController">
 
      <h1>Todos</h1>
 
      <ol id="todos">
        <!-- http://mutablethought.com/2013/04/25/angular-js-ng-repeat-no-longer-allowing-duplicates/ -->
        <li ng-repeat="todo in todos track by $id($index)">
          <input type="checkbox" ng-model="todo.done">
          <span class="done-{{todo.done}}">{{todo.text}}</span> 
          <a class="todo-delete" ng-click="deleteTodo(todo)" href="#">(X)</a>
        </li>
      </ol>
 
      <form id="addNewTodo" ng-submit="add()">
        <p>
          <input id="todoName" name="name" ng-model="todoText" />
          <button type="submit">Add</button>
        </p>
      </form>
 
        <!-- JS -->
        <script type='text/javascript' src='js/jquery-2.1.0.min.js'></script>
        <script type='text/javascript' src='js/angular.min.js'></script>
        <script type='text/javascript' src='js/tech-learning-angular.js'></script>
    </body>
</html>

The full angularJS application, including the simple localStorage wrapper.

// www/js/tech-learning-angular.js
// Test if localStorage is present and supported in the browser
if (typeof(localStorage) == 'undefined') {
  localStorage = new Object();
  localStorage.getItem = function(key) { }
  localStorage.setItem = function(key, value) { }
  localStorage.clear = function() { }
}
 
 
var todoApp = angular.module('tech-learning', [])
  .controller('TodoController', ['$scope', function($scope) {
    $scope.load = function() {
      var todosJSON = localStorage.getItem("todos-angular");
      if (todosJSON) {
        return JSON.parse(todosJSON);
      } else {
        return [];
      }
    }
 
    $scope.todos = $scope.load();
 
    $scope.add = function() {
      $scope.todos.push({text: $scope.todoText, done: false});
      $scope.todoText = '';
      $scope.persist();
    };
 
    $scope.deleteTodo = function(todo) {
      var index = $.inArray(todo, $scope.todos);
      if (index > -1) {
        $scope.todos.splice(index, 1);
      }
      $scope.persist();
    };
 
    $scope.persist = function() {
      localStorage.setItem("todos-angular", JSON.stringify($scope.todos));
    };
  }]);