Smarter ideas worth writing about.

Quick Guide to Angular Best Practices

If you were to search the internet for Angular best practices you’ll find a lot of resources. I found myself constantly googling for a multitude of things when I first started out with Angular. While most of them worked, I kept looking at the code and thinking to myself "there has to be a better way to do things," and asking, "what are other people doing?" It is through these searches that I found community leaders such as Todd Motto and John Papa and learned some lessons from them as well as some lessons from my own experiences. The goal of this synopsis is to empower those of you just starting with Angular and enable you to write clean, easy to maintain code while also not overwhelming you.

Document Structure
When starting a new application using Angular it is recommended to put all of the files related to the angular application into their own folder. When creating our document structure we should adhere to the LIFT principle. LIFT stands for; Locate your code quickly, Identify the code, Flat file structure, and Try to stay DRY (Don’t Repeat Yourself). 

Example:

Notice that by structuring our documents in our application like this it becomes easy to find what we need. If the application gets larger it may make sense to add another folder in the root that we might title Pages and then place our code for each route into its own folder from there such as Pages >> Home and Pages >> Edit. This keeps our tree relatively flat still and also makes things easier to find as the amount of code increases. 

Minification
When writing a large application using Angular, performance can be improved by minifying our javascript files. When I started off a lot of examples I found showed the following to allow for minification of our javascript files. I would then try to structure the code by using return lines and tabs to try to increase its readability.


(function () { 
    'use strict';

    angular.module('app').controller('MyController',
        ['$scope', function MyController($scope) {
            //Controller logic goes here
    }]);

})();

It wasn’t until I came across John Papa’s style guide that I learned about the $inject service. At first I didn’t see the benefit as the way above works, so why change it? The reason for making the change, as I found, comes down to readability. By using the $inject service we can see how our controller becomes easier to understand and follow for anyone that might look at our code as well as still being safe to minify.

(function () {
    'use strict';

    angular.module('app').controller('MyController', MyController);

    MyController.$inject = ['$scope'];

    function MyController($scope) {
        //Controller logic goes here
    };

})();

Notice that the code looks much cleaner, we aren’t indented as much as the first method to minify our source code. It is also much easier to make sure that everything we need to inject is which using the first method can be hard to see as we have more dependencies.

Public Members Up Top
When writing code in other languages it can be very helpful to put the public functions at the top of our code and any private or helper methods below. The same holds true for javascript. Any functions in our controller that can be called from the DOM or any functions available to the controllers in our service should be at the top of our code. This makes it easy at first glance to see what is usable in that section of code. If we also alphabetize the functions as we go down the page it becomes very simple to find our code when we need to make changes to it.

Here’s an example of what not to do.

(function () {
    'use strict';

    angular.module('app').controller('MyController', MyController);

    MyController.$inject = ['$scope', 'MyDataSerice'];

    function MyController($scope, MyDataService) {
        
        $scope.busy = true;
        MyDataService.GetData()
            .then(function (result) {
                $scope.customers = result;
            }, function () {
                console.log("Data retrival failed");
            }).finally(function () {
                $scope.busy = false;
            });

        $scope.save = function (data) {
            MyDataService.SaveData(data)
                .then(function (result) {
                    $scope.customers = result;
                }, function () {
                    console.log("Data retrival failed");
                }).finally(function () {
                    $scope.busy = false;
                });
        };

        $scope.doSomethingElse = function (thing) {
            $scope.thing2 = thing;
        };

    };

})();

This code would work, but can make it difficult to determine what should occur during the page load and what functions can be called from the DOM. If we structure our code as exampled below it becomes easier to follow.

(function () {
    'use strict';

    angular.module('app').controller('MyController', MyController);

    MyController.$inject = ['$scope', 'MyDataSerice'];

    function MyController($scope, MyDataService) {
        
        $scope.doSomethingElse = doSomethingElse;
        $scope.save = save;
        
        activate();

        function activate() {
            $scope.busy = true;
            MyDataService.GetData()
                .then(function (result) {
                    $scope.customers = result;
                }, function () {
                    console.log("Data retrival failed");
                }).finally(function () {
                    $scope.busy = false;
                });
        };

        function doSomethingElse(thing) {
            $scope.thing2 = thing;
        };

        function save(data) {
            MyDataService.SaveData(data)
                    .then(function (result) {
                        $scope.customers = result;
                    }, function () {
                        console.log("Data retrival failed");
                    }).finally(function () {
                        $scope.busy = false;
                    });
        };
    };

})();

Here is an example of a service that follows the same guidelines and also stays DRY.

(function () {
    angular.module('myApp').factory('MyDataService', MyDataService);

    MyDataService.$inject = ['$http', '$q'];

    function MyDataService($http, $q) {

        return {
            getItems: getItems,
            searchItems: searchItems
        }

        function getItems () {
            var deffered = $q.defer();

            $http.get('api/items/')
			   .success(function (response) {
			       var items = makeNewField(response);
			       deffered.resolve(items);
			   }).error(function (response) {
			       deffered.reject(response);
			   });

            return deffered.promise;
        };

        function makeNewField(items) {
            for (var i = 0; i < items.length; i++) {
                items[i].newField = "Field";
            }
            return items;
        };

        function searchItems() {
            var deffered = $q.defer();

            $http.get('api/search/')
			   .success(function (response) {
			       var items = makeNewField(response);
			       deffered.resolve(items);
			   }).error(function (response) {
			       deffered.reject(response);
			   });

            return deffered.promise;
        };
    };
})();

Notice that by putting our return statement at the top we can easily tell which functions are available for the controllers to call. It also alphabetizes the methods in the service in order to be able to find them more easily as the code is expanded.

Separation of Concerns
Starting with the basics, remember this is still an application that is being developed and thus we should adhere to separation of concerns. Our controller should only be responsible for updating our DOM’s data. Therefore any data that needs to be retrieved should be a call to one of our data services. If any other data needs to be saved off into session storage or a cookie, this should also be handled in a data service as it does not affect the DOM. If we are using directives then each directive should have its own file. If the directive needs data from our server, the directive should have its data passed to it by the controller which retrieves the data from our data service.

Controller:


(function () {
    angular.module('myApp').factory('MyDataService', MyDataService);

    MyDataService.$inject = ['$http', '$q'];

    function MyDataService($http, $q) {

        return {
            getItems: getItems
        }

        function getItems () {
            	var deffered = $q.defer();
			
			$http.get('api/items/')
			   .success(function(response) {
				    deffered.resolve(response);
			    }).error(function(response) {
				    deffered.reject(response);
			    });
			
		return deffered.promise;
        }        
    };
})();

Business Logic to Services
Anything that has logic tied to it should be delegated to a service and not in a controller. The reason for this is to keep our code DRY. Rather than implement the same logic in multiple places, let the service handle it. An example of when to do this that I personally encountered was looking up a variation ID of a product selected based on what color and size the user selected as they all had the same source product ID. Rather than let the controller handle this logic I separated it off to my product service and allowed the service to return the variation ID. This allowed the multiple controllers that relied on this logic to only be responsible for updating the data used in the DOM. 

I hope that this has been informative and helpful. My next post will be about recursive angular directives and from there I plan on looking at some of the new things coming with Angular 2.0. You can also read John Papa’s style guide if you would like to get more in depth with Angular’s best practices, https://github.com/johnpapa/angular-styleguide.


Share:

About The Author

Senior Developer

Brian is a senior developer in Cardinal's Charlotte office, primarily focused on front-end development. His skill-set is AngularJS, javascript, jQuery, CSS, HTML5, WebAPI and MVC.