An Angular introduction, and things to keep in mind

Angular is a client side framework aimed at creating rich web applications. It adds logic declaration to the normally static HTML in order to provide a dynamic user experience. Built at Google, it is a perfect tool for building one page applications, and is made to be really accessible for the standard web developper. But some of the magic going on underneath can be tricky, doing things outside the box can be much more difficult. This article has no tutorial or introduction vocation, it just tries to shed some light on some unexpected behaviours of angular.

It's magic

The first thing every one shows about angular is how easy it is to link javascript objects to displayed DOM elements. Even without any scope declaration, angular takes care of everything inside the template. As a result, if you don't need to use data in your controller, you can handle variables as HTML data as if no javascript code was involved.

<!--
  This small sample will display "Hello <content of the input>",
  and update at each keystroke without having to declare anything in javascript
-->
<body ng-app>
  <p>Hello {{name}}</p>
  <input type="text" ng-model="name">
</body>

When data has to be used, the link between the controller and the template consists of only one line. Native directives are here to ease the forms behaviour, and make everything transparent. The elements are binded in both ways, updating the javascript will change the DOM element and vice versa.

Binding methods to events is as easy as binding the models to the display. Native directives allow you to bind click, mouseover, focus or any usual event to javascript methods, with as many different parameters you want.

<!--
  The ng-click directive takes an expression to be evaluated when the user clicks
  on the element.
  It can be a function with parameters, like "setItem(item, $event)"
-->
<body ng-app>
  <p>Items: {{i}}</p>
  <button ng-click="i=i+1">Increase</button>
</body>

The only drawback is that you have to add logic to your HTML. Some people work hard to keep HTML and JS in their respective scopes, to keep display and logic separated, but angular does not embrace this philosophy at all. The only actual issue comes with directives, when you want to use them as elements. Using them as tags is not valid, and they won't work on old version of IE.

As long as you do standard things, you won't have to scratch your head that much, but when tricky cases appear, the usual magic disappears and the learning curve gets a lot steeper.

Full of resources

A great number of internal tools are provided by angular to take care of the usual things, you would have to deal with, inside a web application.

I will not detail every native services here, but here are a few:

  • $route and $routeParams: provides url routing and gives you the url parameters
  • $window: provides a reference to the global window variable
  • $timeout: provides a digested timeout (we will known more about that latter)
  • $location: provides a html5 or native tool to handle redirections
  • $http: provides wrapped xhr methods to perform ajax calls

Even when the native services are not enough, a ton of open source resources are available to handle other cases. If the routing service does not fit your needs, another one adds states or nested routes. If you want to use the twitter bootstrap tools as directives without having to convert their javascript, ui.bootstrap does it for you. If you want to display nvd3 graphs without having to build your own directives, another resource does it for you.

angular.module('angularApp')
.controller('TestCtrl', function ($scope, $routeParams, items) {

  // Do anything here
  $scope.parameter = $routeParams.parameter // From the url
  $scope.items = items.getCollection()      // From the items service

});

Using those modules as dependencies is really simple. Controllers have services dependencies, your app has modules dependencies. Just declare these resources when launching the app, and they will be available. Angular uses dependency injection, we will get back on it later.

No more painful events to handle

When talking about client side frameworks, Backbone, Angular and Ember are recurring names. But they work in very different ways. Backbone and Ember are both based on events handling. You add listeners, remove listeners, bind form elements to javascript objects, bind javascript objects to DOM elements; when a change is made, some of these events are triggered, which goes forth to another state of the application. Backbone works from the javascript only, using css selectors to target DOM elements, Ember invades the HTML a little but keeps a lot of its logic on the javascript side of things.

Angular works in a very different way. There are no events involved, it works with dirty checking. (Well, in fact there are events involved, but only to detect when something changes) When the application is considered dirty, that is when something has changed, everything is redrawn. It can be seen as a humongous waste of resources, given that when some part of a form is altered, you will not actually need to process the other part as well. But as long as you do not keep too many elements on your page, everything works fine. The usual advice, from a ergonomical point of view, is to keep less than 200 elements on a page, adding more of them will only distract the user and will not be beneficial. Angular performances are mainly linked to your computer performances, but when under these typical conditions, the application stays under the 50ms response time and everything goes unseen.

This dirty checking approach has many advantages. There is no need to painfully link DOM elements to their javascript counterparts, no need to clean up the events when the displayed view changes, and no need to handle weird application states. The only thing you have to do is declare things that work together: which variable does this html element display, which variable is linked to this form element, or which function should I call when I click on this. The resulting code is way easier to understand, way more maintainable.

How to keep a clean scope

$apply the magic

When you update things through angular native methods, the dirty checking is automatically made, and everything triggers as wanted. This is called a $digest cycle. The $digest methods iterates through the changes to incrementally update things based on what has changed. To avoid infinite loops, there is a limit on how many $digest iterations can be processed from a single application modification, but if you reach it, it means something is wrong.

The entry point for those $digest calls is the $apply method. After any update, angular will automatically call this $apply function to declare the application as dirty, in order to compute the needed alterations. When calling a function from a template, when updating a model in a form, when initializing a controller or a directive, this $apply call is made. In fact, you would not know of this method existence as long as you do not get to a point where you need it and angular will not do it for you.

The most flagrant example is a timeout. Angular provides the $timeout service to once again call $apply for you, but suppose you want to use the regular setTimeout function. The callback that you pass to setTimeout will be called, but it wont trigger the wanted modifications. The application does not know it is dirty, and will not want to clean itself. In this case, you have to wrap your callback in an $apply call, to ensure the application knows that its state has changed.

angular.module('angularApp')
.controller('TestCtrl', function ($scope, $timeout) {

  $scope.string = 'Hello';

  setTimeout(function(){
    $scope.string = 'This will not be displayed';
  }, 3000);

  setTimeout(function(){
    $apply(function(){
      $scope.string = 'This will be displayed';
    });
  }, 6000);

  $timeout(function(){
    $scope.string = 'This will again be displayed';
  }, 9000);

});

Scope inheritance

Each scope in an angular application inherits from the $rootScope through prototypical inheritance, which only keeps references in sync, and not the variable passed by value (it's actually more complicated than that). If the parent scope has a name attribute, the child scope will be able to access it as long as it does not have a name attribute itself, in which case the parent name attribute will be kept as is, while the child name attribute lives its life on the side. But if you use user.name, as user object is the one stored in the scope, children will be able to access and update the user.name and both scopes will stay in sync.

It is tricky because angular creates scopes on the fly, without you knowing. In all sorts of directives, like ng-repeat (the simple loop directive to iterate over an array) a inherited scope is created. (You can see people using the special $parent attributes which references the parent scope, but it's not reliable since some intermediary scopes could be inserted, and you would end up using $parent.$parent.$parent to fit your needs) That's why the common rule of thumbs is to follow the dot rule and to always keep a dot in your model names.

function TestCtrl($scope){
  $scope.items = [{name: 'Hervé'}, {name: 'Georges'}, {name: 'Robert'}];
  $scope.active = true;
  $scope.model = { isActive: true};
}

<ul>
  <li ng-repeat="item in items">  <!-- A scope is created here -->
    <p>{{item.name}}</p>
    <input type="checkbox" ng-model="active">         <!-- This one won't work properly -->
    <input type="checkbox" ng-model="model.isActive"> <!-- This one will work properly -->
  </li>
</ul>

Where to do what

Angular lets you handle your files as you want, but some logical principles should be followed with the different tools that the framework provides:

Controllers

The main part of every application, it is where most of the logic is kept. It is usually linked to a template, as a javascript counterpart, to declare and use models as wanted. The controller will be called each time the route will be triggered (each time the url changes). This is where you will set the UI actions. What to do when the user clicks there, what to do when this ajax call returns this type of data. It stands as the glue between the rest of the elements.

Services

Those tools are set at first call, and wont be reevaluated later, they are singletons. That's where you will want to store all persistent data. Navigation history, selected items, shop basket, anything that has to be kept between route changes has to be stored in one of these. It can also be used as intermediary layers, or tools. You can use a service to wrap all logic related to a specific model. A service for the users, another for the items, with all the appropriate ajax calls as intermediary methods.

Directives

The directives are the only things that should be used to interact with the DOM (some specific services could, in odd cases). They work like some kind of helpers to add UI logic to some DOM elements. The first big difficulty you will encounter when using angular will be to understand these, and to get them to work. Restricted scope, compile function, link function, shared controllers, there are some tricky aspects underlying their use.

Filters

Filters are used to change data on the fly at display. If you want to sort or filter an array, capitalize a word, display a date in a human way, a filter is the solution. They have to be fast, as they re evaluated at each $digest cycle, so don't put heavy treatments in here.

Dependency injection

Dependencies are easy to use, you just have to use arguments with appropriate names and Angular will handle it fine. Any service can be called from anywhere in the application. The only think to care for are circular dependencies, but the console will inform you fully if such a thing happens. But once your application works fine and you want to make it pretty, you minify all your scripts in one big ugly javascript file and everything stops working. Usual minification tools rename variables, which breaks angular dependency injection. To prevent this issue, two solutions are available: setting the list of dependencies at controller creation as strings, or adding this information later with a special declaration. The first one keeps things in the same place so it's my favorite:

// This declaration will break through minification as the arguments will be renamed
angular.module('angularApp')
.controller('TestCtrl', function ($scope, $routeParams, items) {

  // Do anything here
  $scope.parameter = $routeParams.parameter // From the url
  $scope.items = items.getCollection()      // From the items service

});

// This one is minification proof
angular.module('angularApp')
.controller('TestCtrl', ['$scope', '$routeParams', 'items', function ($scope, $routeParams, items) {

  // Do anything here
  $scope.parameter = $routeParams.parameter // From the url
  $scope.items = items.getCollection()      // From the items service

}]);

Then it's no longer the variable name that counts, but the order of the arguments. Each one of them will receive the appropriate dependency from the string array. There is another place where this arguments order is at play, in the directives linking function.

// The directive dependencies work as regular controller ones
angular.module('angularApp')
.directive('testDirective', ['$timeout', function($timeout) {
  return {
    template: '<div class="test">Hello {{name}}</div>',

    // The link function takes ordered elements
    link: function(scope, element, attr) {
      scope.name = 'Georges';
    }
  };
}]);

This linking function takes 4 arguments, the scope, the targeted element, its attributes, and a potential controller. The thing to note is that in directives you usually use the scope as scope and not $scope because of this. You're not using a $scope dependency, only a passed parameter.

How to keep your sanity despite the asynchronicity

The web is asynchronous. You can not predict how much time a http request will take, and have to behave accordingly. The basic approach is to use the native callbacks triggered by the responses, angular uses a promises library to wrapped those up, a library called $q.

// Those two samples do the same things

$.get('/some-url', function(data){
  // A regular function
  console.log(data);
}, 'json');

$http.get('some-url').then(function(data){
  // Inside a promise
  console.log(data);
});

$q provides a way to handle parallel promises with the .all() method, and promises are by design chainable to handle calls in series. So even if this tool does not give as much control over the flow as async (a great library which works with callbacks and not promises), you can deal with every day scenarios without worries.

// Parallel calls
$q.all([
  $http.get('/url1'),
  $http.get('/url2'),
  $http.get('/url3')
]).then(function(responses){
  console.log(responses[0]);
  console.log(responses[1]);
  console.log(responses[2]);
});

// Serie calls
$http.get('/url1').then(function(response){
  console.log(response);
  return $http.get('/url2');
}).then(function(response){
  console.log(response);
  return $http.get('/url3');
}).then(function(response){
  console.log(response);
});

Angular will automatically wrap those calls to $apply the changes, but some times you need to compute things that are not handle by the $digest cycle asynchronously, and that is when you want to $watch.

First case: An included controller that has to wait for its parent to load resources. You can call an initialization method from the parent in order to trigger logic in the child, but it is rather hard to maintain since both the controllers would be strongly linked. A different approach is to watch for some changes in the child, and let it live on his own.

angular.module('angularApp')
.controller('ChildCtrl', ['$scope', function ($scope) {

  function logData(){
    console.log($scope.item);
  };

  // $scope.item is loaded asynchronously from the parent controller
  $scope.$watch('item', function(){
    logData();
  });
}]);

Second Case: A directive that updates a graph when the data changes You may want to process things in a directive after its first initialization, when some underlying data changes for exemple. One solution would be to set a user triggered button, but it looses some of its value. Another way to do it is once again to $watch for these data changes.

Each one of those listeners is automatically evaluated at each $digest cycle, so you may want to keep them under control. You can delete a $watch statement by calling the function returned by its first call. One of the few listeners removal still needed inside angular.

Conclusion

Angular aims to provide HTML with the dynamic notions that are needed in its current use, and does it quite well. Removing the boilerplate code that was typically needed to link the DOM to the underlying javascript has made it really easy to create great interfaces way quicker. Even though it lets the developper handle things as he sees fit, some good practices are needed and are not enforced. Many solutions can achieve the same purpose, but only some of them will be appropriate. As with every framework, different tasks are to be done in their designated place; but angular wont lock you in this perspective, and will let you do bad things without noticing. It has proven to be a really great tool that fits the current web trends well; easy to discover but harder to master, I hope those few lines helped.

Comments