AngularJS Do’s and Dont’s

Since the past year, I have been observing the market and adoption of AngularJS in the wild. I really like what I am seeing. It surely has come a long way from being called a toy framework, esoteric research framework to where it is today.

Having worked with this framework from the start and having answered quite a few questions about it on Stack Overflow I have noticed a few common mistakes that people do when working with AngularJS. I will in this post attempt to address a few of these problems and what would be the right way to do the same.

DONT : Access DOM in the Controller

This is the most common pitfall and probably the one that has been covered quite a lot in both the official site and elsewhere. I wont bother to add much to this except I will try to analyse WHY this is a mistake.

I hope anyone who has been in the field of programming for any considerable time should have heard about the Law of Demeter. Here is an excerpt from Wikipedia

a given object should assume as little as possible about the structure or properties of anything else.

In this context, the controller should not know anything about the DOM object that it is trying to manipulate.

This is important for multiple reasons. Prominently, testing. At this point you might be thinking “Ah, But I dont believe in testing” or “I hate TDD” or some other crap like that. But I am not asking you to convert your religion. I am saying that whether you believe in testing or not, you are already doing it. Each time you write a piece of code, dont you test it? Even if you are doing it manually – you are testing it.

Having the controller tightly bound to your DOM object gives you additional things to keep in mind. Each additional thing to keep in mind will increase the chances of you making mistakes. This according to me is the single most important thing with Angular. It totally reduces the amount of things you need to keep in your head.

DONT: Use Selectors in Directives

Oh yeah. You dint think I would have a problem with that, did you? Well, I do and here is the reason why. Consider the very essence of how Angular is built. AngularJS is built with the idea that, HTML can be extended, which means that each element ( which is what you get when you run a selector! ) can be extended.

To properly analyse this scenario, we need to consider what a directive really is. To answer it simply, it is a command that you attach to an existing element and instruct it / direct it..( get it? directive! ) to do what additional things you want.

When you are attaching a directive, you attach it to a particular element. You define, in the directive, the additional operations that that element has to do. You are extending that element to perform more than what it was defined ( in the browser ) to do.

Consider what you are doing when you run a selector inside the directive’s linking function. You are basically, requesting access to a DOM node – other than the DOM node to which this directive ( you are in the linking function right? ) is attached. So, each time you feel like you need to run a “selector” – it is time to write another directive. There are multiple advantages of doing this.

  1. Your directives will start becoming more and more reusable ( This is a huge advantage – trust me! ).
  2. Each directive will just do one thing and one thing alone which means they are easy to validate and most importantly – give names to.
    If you properly name your directives, all that you need to do to go through what the code is doing is go through the html.

DONT: Use Scope elsewhere ( other than controller )

The controller is the right place to do all and everything that is related to the scope. It is the place where you write all the

$scope.$watch(...)

and define all the $scope functions that you need to access from your views ( like event handlers ). Generally, the event handler is a plan function which will in turn call a function a service.

    $scope.onLoginButtonClick = function(){
        AuthenticationService.login($scope.username,
            $scope.password);
    };

On very rare occasions you can add a promise success handler in there.

DONT: Write business logic in controllers

There was a very specific reason why the earlier example was like that. It showed you a $scope function that was in turn calling a function in a service. The controller is not responsible for the login mechanism or how login happens. If you write this code in a service, you are decoupling the service from the controller which means anywhere else that you want to use the same service, all that you need to do is, inject and fire away the function.

DO: data in Services

If there is one thing, I think Angular ( or angularians ) need(s) to steal from Backbone, then it is the way Backbone structures its Models. In Backbone, the Backbone.Model class is responsible for both the end point and storing the data. We should probably follow the same logic when using Angular. The advantages of this are amazing in Angular.

This is how I typically define my Services.

(function () {
    var person = angular.module("person");
    person.factory("PersonService", [
        '$http',
        'httpRestValue',
        function ($http, httpRestValue) {
            var PersonService = {
                data: {
                    currentPerson: {},
                    persons : []
                },
                getPerson: function (id) {
                    return $http.get(httpRestValue + "person/"+id)
                        .success(function success(data) {
                            PersonService.data.currentPerson = data;
                        })
                        .error(function error() {
                        });
                },
                getPersons : function(){
                    return $http.get(httpRestValue + "person/list")
                        .success(function success(data) {
                            PersonService.data.persons = data;
                        })
                        .error(function error() {
                        });
                },
                savePerson : function(person){
                    return $http.post(httpRestValue + "person/",person)
                        .success(function success() {
                            PersonService.getPersons();
                        })
                        .error(function error() {
                        });
                },
                deletePerson : function(id){
                    return $http.delete(httpRestValue + "person/"+id)
                        .success(function success() {
                            PersonService.getPersons();
                        })
                        .error(function error() {
                        });
                }
        };
        return PersonService;
    }
]);
})();

The important thing to note is that the

data

is stored in the same service ( under a data attribute ). Also at the end of each post or delete we refresh the data attribute by calling the

getPersons

from inside

deletePerson

and

savePerson

success handlers.

The corresponding controller would do something like :

(function () {
    var person = angular.module("person");
    person.controller("PersonController",[
        '$scope',
        'PersonService',
        function($scope,PersonService){
            $scope.personData = PersonService.data;
        }
    ]);
})();

The greatest advantage of this is that the Controller’s data automatically refreshes whenever you do a save or a delete on the person. Also, if you perform any other action and you wish to refresh the data, the call to do that is just one line. It gives you a fire and forget strategy for all your data.

Maybe you might want to clear some of the most common confusion points in Angular or perhaps see how Angular stacks up with Backbone? Or how it stacks up with KnockoutJS perhaps?

6 comments on “AngularJS Do’s and Dont’s

  1. Great article.

    While I appreciate the cautionary tid-bits, I think it would also be helpful to counter them with an appropriate, perhaps ‘more Angular’ way of doing things.

    Particularly when it comes to not referencing DOM in the controller.

    I mean, if I saw someone using querySelectorAll where they could’ve used a declarative solution the answer would be obvious.

    What about using ng-click to submit to a scope method a collection for form values for an XHR payload.

    Is it still bad form to do something like this:

    $scope.submitForm = function() {
    var data = {
    username: document.querySelectorAll(‘#user-name’)[0].value,
    *etc*
    }
    }

    How could we use Angular here to capture/reference these elements without direct DOM access?

    • I’m new to Angular but I’m assuming you’re wanting something like this:

      *(your comment is old, so you’ve probably already figured this out)

      Then in your controller, provided the controller’s scope is wrapping this input field, you can just access it as $scope.username

    • Wrapping it in an IIFE keeps variables out of the global scope (avoiding potential name collisions), so in this particular case the person variable has function-level scope and is not available in any other script.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>