bytes for thought by François Fortin

AngularJS tips and tricks for neophytes and the all-around curious developer.

AngularJS changes the face of web development, but a steep learning curve can be a strong deterrent to newcomers. But worry not! for we will cover a series of handy tips that may save you countless hours of pain and anger.

Article written as of AngularJS 1.0.7/1.1.5.

Forget jQuery’s event handling altogether.

I know it’s hard at first, but the temptation to use jQuery is the first and most common mistake developers make when starting out with Angular. It is so deeply engrained in our minds that you must force yourself to perform a paradigm shift. Since the entire application now has much fewer physical entry points (i.e. HTML files) – likely a single one – events such as $(document).ready() will only be triggered once throughout the entire application (on the initial load), and not with every Angular view change. This means you can’t rely on jQuery’s event handling anymore.

Angular’s handling of the DOM will inevitably clash with that of jQuery, unless handled properly, and that is, through «Directives». Directives are one of the very few places where you should allow yourself to use jQuery with Angular. They are literally new DOM elements (or attributes) generated by and for Angular, which are compiled and linked whenever the DOM is updated. When you would before have a handler for the “onclick” event, you will now use the ng-click directive. If you need to use an external jQuery module, such as DataTables or FullCalendar, they must first and foremost be wrapped into a proper Angular directive.

Luckily for us, however, others have gone through the hassle and wrapped common widgets and librairies for us, under the AngularUI toolkit. Writing directives on your own can be somewhat daunting, and the subject exceeds the scope of this article.

But… where is Angular’s “DOMready” event, if I can’t use jQuery’s $(document).ready()?

That’s the tricky part. At some point, you will need to trigger certain actions when the DOM is ready, and not before. Angular doesn’t provide you with an event as convenient as jQuery’s ready(), but there is a workaround, in the form of the $timeout injectable service.

Using $timeout’s in general in Javascript isn’t exactly elegant, but until there is a better way to handle it, it does the trick. Generally, however, if you’re using directives – as you should -, you probably won’t need to resort to using $timeout. It can however be useful if you’re writing an injectable service which requires that DOM elements be ready prior to running. So before you use this trick, ask yourself if your problem couldn’t be better handled in a directive’s link function.

$timeout(function(){console.log("DOM is ready! yay!");});

If not, simply wrap whatever you would’ve called in your $(document).ready() event handler into a $timeout (used exactly in the same way as Javascript’s native setTimeout). Without a time interval set, it will place your function at the end of the call stack, effectively executing it after all DOM elements have been properly compiled and linked into place by Angular.

Avoid the $scope.$apply() madness, use $scope.safeApply().

One of Angular’s coolest and fundamental features is the $scope service. This jsfiddle example showcases some of the $scope basic features and behavior, and is worth a look, especially if you are new with Angular.

When $scope variables are updated directly from within a form input (typically through the ng-model directive), all references are updated automatically for you, and events are emitted accordingly. However, there comes a time when you will need to handle $scope variables yourself, within the controller (or within a directive). In such cases, for changes to propagate, you need to call $scope.$apply(). However, if this call leads to more changes, which, in turn, cascade into more $scope.$apply() calls, in turn producing changes resulting in more $scope.$apply() invokes, the situation quickly deteriorates. Angular will also block such occurences, throwing an error if an $apply() is called while another $apply() is currently being executed.

To go around this problem, Andrew Reutter came up with a very simple, yet elegant, safeApply method. I highly recommend you read his article, and begin using safeApply(), especially if your project is still in its infancy stages.

The blob controller and dangers of inherited $scopes.

The blob is a common anti-pattern, and is somewhat easy to fall into when using Angular, in a not-so-obvious way.

First of all, it is worth pointing out that Angular supports (and encourages) the use of nested controllers. This is good practice, resulting in cleaner, readable and maintainable code.

However, children controllers will inherit their parent’s $scope, and that’s where things can get out of hand if coding conventions are loose, or simply non-existant. Function name clashes can occur, and $scope.$apply() calls can have unexpected repercussions, as the changes can propagate to the whole $scope tree.

There is no perfect way to go around this – but make sure functions that belong in the parent $scope are in the parent – and not copy-pasted around children. Make sure to give your functions clear and logical names, and don’t put anything in the $scope object that doesn’t belong there. Private variables and functions should remain in the private closure of the controller, so as not to pollute the $scope.

Every time the URL is updated, Angular triggers a new route and all of my context is lost, forcing me to rebuild all of the data!

This is one of the most annoying aspects of Angular. Certainly you won’t want to rebuild your context and reload all your data on every route change – but Angular has very poor native support for clean URLs with seamless parameters (i.e. http://example.com/#/user/1 instead of http://example.com/#/user?id=1). Changing the ID in that first URL to, for example, 2 instead of 1, will trigger an entire route change in Angular, causing all your context to be flushed, and forcing unnecessary calls to your server to retrieve the destroyed data.

Sadly, there are two options at this point, none of which are very interesting – although the latter will work with the least effort. The first solution is to catch the $routeChangeStart event and cancel the route change when the change in the URL doesn’t require a full rerouting. However, this requires more work, as you need to parse the URL yourself, and detect whether or not you should take an action.

The second solution is to use search parameters (i.e. ?id=1), and set the reloadOnSearch option to false in the $routeProvider service. While this is much less elegant, since it results in dirty URLs, it is also much simpler to implement. Your route definition should look like this:

$routeProvider
     .when(
        "/film/list",
         {
          templateUrl: '/film/list.html', 
          controller: FilmListController,
          reloadOnSearch: false
          } 
          ....
     )

From there on, you have to listen to the $routeUpdate event, and manually update your context depending on the new search params. In your controller, it would look something like this:

$scope.$on('$routeUpdate', function(){
  var newId = $location.search().id;
  updateContext(newId);
  ...
});

Note that with this technique, any change to the URL other than to the search parameters (i.e. any change before the “?”) will still trigger a complete route change in Angular.

Where to go from here?

AngularJS is still quite new, and while its documentation is growing steadily, the time investment required to be fluent with it is still impressive. I would highly recommend investing in a book; O’Reilly’s AngularJS is probably the best place to get started.

Thank you for reading, and good luck with Angular!