Our AngularJS style guide
Switch branches/tags
Nothing to show
Clone or download
Pull request Compare This branch is 13 commits ahead, 993 commits behind johnpapa:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
assets
LICENSE
README.md

README.md

Econsultancy AngularJS Style Guide

This is a fork of the excellent johnpapa/angularjs-styleguide, customised to fit into our workflow and applications. Some sections were removed for brevity.

Table of Contents

  1. Single Responsibility
  2. IIFE
  3. Modules
  4. Controllers
  5. Services
  6. Factories
  7. Data Services
  8. Directives
  9. Resolving Promises for a Controller
  10. Manual Dependency Injection
  11. Minification and Annotation
  12. Exception Handling
  13. Naming
  14. Modularity
  15. Module Structure
  16. Angular $ Wrapper Services
  17. Comments
  18. Constants

Single Responsibility

  • Rule of 1: Define 1 component per file.

    The following example defines the app module and its dependencies, defines a controller, and defines a factory all in the same file.

    /* avoid */
    angular
    	.module('app', ['ngRoute'])
    	.controller('SomeController' , SomeController)
    	.factory('someFactory' , someFactory);
    
    function SomeController() { }
    
    function someFactory() { }

    The same components are now separated into their own files.

    /* recommended */
    
    // app_module.js
    angular
    	.module('app', ['ngRoute']);
    /* recommended */
    
    // someController.js
    angular
    	.module('app')
    	.controller('SomeController' , SomeController);
    
    function SomeController() { }
    /* recommended */
    
    // someFactory.js
    angular
    	.module('app')
    	.factory('someFactory' , someFactory);
    
    function someFactory() { }

Back to top

IIFE

  • IIFE: Wrap AngularJS components in an Immediately Invoked Function Expression (IIFE).

Why?: An IIFE removes variables from the global scope. This helps prevent variables and function declarations from living longer than expected in the global scope, which also helps avoid variable collisions.

Why?: When your code is minified and bundled into a single file for deployment to a production server, you could have collisions of variables and many global variables. An IIFE protects you against both of these by providing variable scope for each file.

```javascript
/* avoid */
// logger.js
angular
  .module('app')
  .factory('logger', logger);

// logger function is added as a global variable
function logger () { }

// storage.js
angular
  .module('app')
  .factory('storage', storage);

// storage function is added as a global variable
function storage () { }
```


```javascript
/**
 * recommended
 *
 * no globals are left behind
 */

// logger.js
(function () {
  'use strict';

  angular
    .module('app')
    .factory('logger', logger);

  function logger () { }
})();

// storage.js
(function () {
  'use strict';

  angular
    .module('app')
    .factory('storage', storage);

  function storage () { }
})();
```
  • Note: For brevity only, the rest of the examples in this guide may omit the IIFE syntax.

Back to top

Modules

  • Definitions (aka Setters): Declare modules without a variable using the setter syntax.

    Why?: With 1 component per file, there is rarely a need to introduce a variable for the module.

    /* avoid */
    var app = angular.module('app', [
        'ngAnimate',
        'ngRoute',
        'dsShared',
        'dsDashboard'
    ]);

    Instead use the simple setter syntax.

    /* recommended */
    angular
    	.module('app', [
        'ngAnimate',
        'ngRoute',
        'dsShared',
        'dsDashboard'
    ]);
  • Getters: When using a module, avoid using a variables and instead use chaining with the getter syntax.

    Why? : This produces more readable code and avoids variables collisions or leaks.

    /* avoid */
    var app = angular.module('app');
    app.controller('SomeController' , SomeController);
    
    function SomeController() { }
    /* recommended */
    angular
      .module('app')
      .controller('SomeController' , SomeController);
    
    function SomeController() { }
  • Setting vs Getting: Only set once and get for all other instances.

    Why?: A module should only be created once, then retrieved from that point and after.

    • Use angular.module('app', []); to set a module.
    • Use angular.module('app'); to get a module.
  • Named vs Anonymous Functions: Use named functions instead of passing an anonymous function in as a callback.

    Why?: This produces more readable code, is much easier to debug, and reduces the amount of nested callback code.

    /* avoid */
    angular
      .module('app')
      .controller('Dashboard', function () { });
      .factory('logger', function () { });
    /* recommended */
    
    // dashboard.js
    angular
      .module('app')
      .controller('Dashboard', Dashboard);
    
    function Dashboard () { }
    // logger.js
    angular
      .module('app')
      .factory('logger', logger);
    
    function logger () { }

Back to top

Controllers

  • controllerAs View Syntax: Use the controllerAs syntax over the classic controller with $scope syntax.

    Why?: Controllers are constructed, "newed" up, and provide a single new instance, and the controllerAs syntax is closer to that of a JavaScript constructor than the classic $scope syntax.

    Why?: It promotes the use of binding to a "dotted" object in the View (e.g. customer.name instead of name), which is more contextual, easier to read, and avoids any reference issues that may occur without "dotting".

    Why?: Helps avoid using $parent calls in Views with nested controllers.

    <!-- avoid -->
    <div ng-controller="Customer">
      {{ name }}
    </div>
    <!-- recommended -->
    <div ng-controller="Customer as customer">
      {{ customer.name }}
    </div>
  • controllerAs Controller Syntax: Use the controllerAs syntax over the classic controller with $scope syntax.

  • The controllerAs syntax uses this inside controllers which gets bound to $scope

    Why?: controllerAs is syntactic sugar over $scope. You can still bind to the View and still access $scope methods.

    Why?: Helps avoid the temptation of using $scope methods inside a controller when it may otherwise be better to avoid them or move them to a factory. Consider using $scope in a factory, or if in a controller just when needed. For example when publishing and subscribing events using $emit, $broadcast, or $on consider moving these uses to a factory and invoke from the controller.

    /* avoid */
    function Customer ($scope) {
      $scope.name = {};
      $scope.sendMessage = function () { };
    }
    /* recommended - but see next section */
    function Customer () {
      this.name = {};
      this.sendMessage = function () { };
    }
  • controllerAs with vm: Use a capture variable for this when using the controllerAs syntax. Choose a consistent variable name such as vm, which stands for ViewModel.

    Why?: The this keyword is contextual and when used within a function inside a controller may change its context. Capturing the context of this avoids encountering this problem.

    /* avoid */
    function Customer () {
      this.name = {};
      this.sendMessage = function () { };
    }
    /* recommended */
    function Customer () {
      var vm = this;
      vm.name = {};
      vm.sendMessage = function () { };
    }
    • Note: You can avoid any jshint warnings by placing the comment below above the line of code.
    /* jshint validthis: true */
    var vm = this;
    • Note: When creating watches in a controller using controller as, you can watch the vm.* member using the following syntax. (Create watches with caution as they add more load to the digest cycle.)
    $scope.$watch('vm.title', function(current, original) {
        $log.info('vm.title was %s', original);
        $log.info('vm.title is now %s', current);
    });
  • Bindable Members Up Top: Place bindable members at the top of the controller, alphabetized, and not spread through the controller code.

    Why?: Placing bindable members at the top makes it easy to read and helps you instantly identify which members of the controller can be bound and used in the View.

    Why?: Setting anonymous functions in-line can be easy, but when those functions are more than 1 line of code they can reduce the readability. Defining the functions below the bindable members (the functions will be hoisted) moves the implementation details down, keeps the bindable members up top, and makes it easier to read.

    /* avoid */
    function Sessions() {
        var vm = this;
    
        vm.gotoSession = function() {
          /* ... */
        };
        vm.refresh = function() {
          /* ... */
        };
        vm.search = function() {
          /* ... */
        };
        vm.sessions = [];
        vm.title = 'Sessions';
    /* recommended */
    function Sessions() {
        var vm = this;
    
        vm.gotoSession = gotoSession;
        vm.refresh = refresh;
        vm.search = search;
        vm.sessions = [];
        vm.title = 'Sessions';
    
        ////////////
    
        function gotoSession() {
          /* */
        }
    
        function refresh() {
          /* */
        }
    
        function search() {
          /* */
        }

    Controller Using "Above the Fold"

    • Note: If the function is a 1 liner consider keeping it right up top, as long as readability is not affected.
    /* avoid */
    function Sessions(data) {
        var vm = this;
    
        vm.gotoSession = gotoSession;
        vm.refresh = function() {
            /**
             * lines
             * of
             * code
             * affects
             * readability
             */
        };
        vm.search = search;
        vm.sessions = [];
        vm.title = 'Sessions';
    /* recommended */
    function Sessions(dataservice) {
        var vm = this;
    
        vm.gotoSession = gotoSession;
        vm.refresh = dataservice.refresh; // 1 liner is OK
        vm.search = search;
        vm.sessions = [];
        vm.title = 'Sessions';
  • Function Declarations to Hide Implementation Details: Use function declarations to hide implementation details. Keep your bindable members up top. When you need to bind a function in a controller, point it to a function declaration that appears later in the file. This is tied directly to the section Bindable Members Up Top. For more details see this post.

    Why?: Placing bindable members at the top makes it easy to read and helps you instantly identify which members of the controller can be bound and used in the View. (Same as above.)

    Why?: Placing the implementation details of a function later in the file moves that complexity out of view so you can see the important stuff up top.

    Why?: Function declaration are hoisted so there are no concerns over using a function before it is defined (as there would be with function expressions).

    Why?: You never have to worry with function declarations that moving var a before var b will break your code because a depends on b.

    Why?: Order is critical with function expressions

    /**
     * avoid
     * Using function expressions.
     */
    function Avengers(dataservice, logger) {
        var vm = this;
        vm.avengers = [];
        vm.title = 'Avengers';
    
        var activate = function() {
            return getAvengers().then(function() {
                logger.info('Activated Avengers View');
            });
        }
    
        var getAvengers = function() {
            return dataservice.getAvengers().then(function(data) {
                vm.avengers = data;
                return vm.avengers;
            });
        }
    
        vm.getAvengers = getAvengers;
    
        activate();
    }
  • Notice that the important stuff is scattered in the preceding example.

  • In the example below, notice that the important stuff is up top. For example, the members bound to the controller such as vm.avengers and vm.title. The implementation details are down below. This is just easier to read.

    /*
     * recommend
     * Using function declarations
     * and bindable members up top.
     */
    function Avengers(dataservice, logger) {
        var vm = this;
        vm.avengers = [];
        vm.getAvengers = getAvengers;
        vm.title = 'Avengers';
    
        activate();
    
        function activate() {
            return getAvengers().then(function() {
                logger.info('Activated Avengers View');
            });
        }
    
        function getAvengers() {
            return dataservice.getAvengers().then(function(data) {
                vm.avengers = data;
                return vm.avengers;
            });
        }
    }
  • Defer Controller Logic: Defer logic in a controller by delegating to services and factories.

    Why?: Logic may be reused by multiple controllers when placed within a service and exposed via a function.

    Why?: Logic in a service can more easily be isolated in a unit test, while the calling logic in the controller can be easily mocked.

    Why?: Removes dependencies and hides implementations details from the controller.

    /* avoid */
    function Order ($http, $q) {
      var vm = this;
      vm.checkCredit = checkCredit;
      vm.total = 0;
    
      function checkCredit () {
        var orderTotal = vm.total;
        return $http.get('api/creditcheck').then(function (data) {
            var remaining = data.remaining;
            return $q.when(!!(remaining > orderTotal));
        });
      };
    }
    /* recommended */
    function Order (creditService) {
      var vm = this;
      vm.checkCredit = checkCredit;
      vm.total = 0;
    
      function checkCredit () {
        return creditService.check();
      };
    }
  • Assigning Controllers: When a controller must be paired with a view and either component may be re-used by other controllers or views, define controllers along with their routes.

    • Note: If a View is loaded via another means besides a route, then use the ng-controller="Avengers as vm" syntax.

    Why?: Pairing the controller in the route allows different routes to invoke different pairs of controllers and views. When controllers are assigned in the view using ng-controller, that view is always associated with the same controller.

 /* avoid - when using with a route and dynamic pairing is desired */

 // route-config.js
 angular
   .module('app')
   .config(config);

 function config ($routeProvider) {
   $routeProvider
     .when('/avengers', {
       templateUrl: 'avengers.html'
     });
 }
 ```

 ```html
 <!-- avengers.html -->
 <div ng-controller="Avengers as vm">
 </div>
 ```

 ```javascript
 /* recommended */

 // route-config.js
 angular
   .module('app')
   .config(config);

 function config ($routeProvider) {
   $routeProvider
     .when('/avengers', {
       templateUrl: 'avengers.html',
       controller: 'Avengers',
       controllerAs: 'vm'
     });
 }
 ```

 ```html
 <!-- avengers.html -->
 <div>
 </div>
 ```

**[Back to top](#table-of-contents)**

## Services

- **Singletons**: Services are instantiated with the `new` keyword, use `this` for public methods and variables. Can also use a factory, which I recommend for consistency.

 - Note: [All AngularJS services are singletons](https://docs.angularjs.org/guide/services). This means that there is only one instance of a given service per injector.

 ```javascript
 // service

 angular
     .module('app')
     .service('logger', logger);

 function logger () {
   this.logError = function (msg) {
     /* */
   };
 }
 ```

 ```javascript
 // factory
 angular
     .module('app')
     .factory('logger', logger);

 function logger () {
   return {
     logError: function (msg) {
       /* */
     }
   };
 }
 ```

**[Back to top](#table-of-contents)**

## Factories

- **Single Responsibility**: Factories should have a [single responsibility](http://en.wikipedia.org/wiki/Single_responsibility_principle), that is encapsulated by its context. Once a factory begins to exceed that singular purpose, a new factory should be created.

- **Singletons**: Factories are singletons and return an object that contains the members of the service.

 - Note: [All AngularJS services are singletons](https://docs.angularjs.org/guide/services).

- **Accessible Members Up Top**: Expose the callable members of the service (it's interface) at the top, using a technique derived from the [Revealing Module Pattern](http://addyosmani.com/resources/essentialjsdesignpatterns/book/#revealingmodulepatternjavascript).

 *Why?*: Placing the callable members at the top makes it easy to read and helps you instantly identify which members of the service can be called and must be unit tested (and/or mocked).

 *Why?*: This is especially helpful when the file gets longer as it helps avoid the need to scroll to see what is exposed.

 *Why?*: Setting functions as you go can be easy, but when those functions are more than 1 line of code they can reduce the readability and cause more scrolling. Defining the callable interface via the returned service moves the implementation details down, keeps the callable interface up top, and makes it easier to read.

 ```javascript
 /* avoid */
 function dataService () {
   var someValue = '';
   function save () {
     /* */
   };
   function validate () {
     /* */
   };

   return {
     save: save,
     someValue: someValue,
     validate: validate
   };
 }
 ```

 ```javascript
 /* recommended */
 function dataService () {
   var someValue = '';
   var service = {
     save: save,
     someValue: someValue,
     validate: validate
   };
   return service;

   ////////////
   function save () {
     /* */
   };

   function validate () {
     /* */
   };
 }
 ```

- This way bindings are mirrored across the host object, primitive values cannot update alone using the revealing module pattern

   ![Factories Using "Above the Fold"](https://raw.githubusercontent.com/johnpapa/angularjs-styleguide/master/assets/above-the-fold-2.png)

- **Function Declarations to Hide Implementation Details**: Use function declarations to hide implementation details. Keep your acessible members of the factory up top. Point those to function declarations that appears later in the file. For more details see [this post](http://www.johnpapa.net/angular-function-declarations-function-expressions-and-readable-code).

 *Why?*: Placing accessible members at the top makes it easy to read and helps you instantly identify which functions of the factory you can access externally.

 *Why?*: Placing the implementation details of a function later in the file moves that complexity out of view so you can see the important stuff up top.

 *Why?*: Function declaration are hoisted so there are no concerns over using a function before it is defined (as there would be with function expressions).

 *Why?*: You never have to worry with function declarations that moving `var a` before `var b` will break your code because `a` depends on `b`.

 *Why?*: Order is critical with function expressions

 ```javascript
 /**
  * avoid
  * Using function expressions
  */
  function dataservice($http, $location, $q, exception, logger) {
     var isPrimed = false;
     var primePromise;

     var getAvengers = function() {
       // implementation details go here
     };

     var getAvengerCount = function() {
       // implementation details go here
     };

     var getAvengersCast = function() {
       // implementation details go here
     };

     var prime = function() {
       // implementation details go here
     };

     var ready = function(nextPromises) {
       // implementation details go here
     };

     var service = {
         getAvengersCast: getAvengersCast,
         getAvengerCount: getAvengerCount,
         getAvengers: getAvengers,
         ready: ready
     };

     return service;
 }
 ```

 ```javascript
 /**
  * recommended
  * Using function declarations
  * and accessible members up top.
  */
 function dataservice($http, $location, $q, exception, logger) {
     var isPrimed = false;
     var primePromise;

     var service = {
         getAvengersCast: getAvengersCast,
         getAvengerCount: getAvengerCount,
         getAvengers: getAvengers,
         ready: ready
     };

     return service;

     ////////////

     function getAvengers() {
       // implementation details go here
     }

     function getAvengerCount() {
       // implementation details go here
     }

     function getAvengersCast() {
       // implementation details go here
     }

     function prime() {
       // implementation details go here
     }

     function ready(nextPromises) {
       // implementation details go here
     }
 }
 ```


**[Back to top](#table-of-contents)**

## Data Services

- **Separate Data Calls**: Refactor logic for making data operations and interacting with data to a factory. Make data services responsible for XHR calls, local storage, stashing in memory, or any other data operations.

 *Why?*: The controller's responsibility is for the presentation and gathering of information for the view. It should not care how it gets the data, just that it knows who to ask for it. Separating the data services moves the logic on how to get it to the data service, and lets the controller be simpler and more focused on the view.

 *Why?*: This makes it easier to test (mock or real) the data calls when testing a controller that uses a data service.

 *Why?*: Data service implementation may have very specific code to handle the data repository. This may include headers, how to talk to the data, or other services such as $http. Separating the logic into a data service encapsulates this logic in a single place hiding the implementation from the outside consumers (perhaps a controller), also making it easier to change the implementation.

 ```javascript
 /* recommended */

 // dataservice factory
 angular
     .module('dsCore')
     .factory('dataservice', dataservice);

 dataservice.$inject = ['$http', 'logger'];

 function dataservice($http, logger) {
     return {
         getAvengers: getAvengers
     };

     function getAvengers() {
         return $http.get('/api/maa')
             .then(getAvengersComplete)
             .catch(getAvengersFailed);

         function getAvengersComplete(response) {
             return response.data.results;
         }

         function getAvengersFailed(error) {
             logger.error('XHR Failed for getAvengers.' + error.data);
         }
     }
 }
 ```
 - Note: The data service is called from consumers, such as a controller, hiding the implementation from the consumers, as shown below.

 ```javascript
 /* recommended */

 // controller calling the dataservice factory
 angular
     .module('dsAvengers')
     .controller('Avengers', Avengers);

 Avengers.$inject = ['dataservice', 'logger'];

 function Avengers(dataservice, logger) {
     var vm = this;
     vm.avengers = [];

     activate();

     function activate() {
         return getAvengers().then(function() {
             logger.info('Activated Avengers View');
         });
     }

     function getAvengers() {
         return dataservice.getAvengers()
           .then(function (data) {
               vm.avengers = data;
               return vm.avengers;
           });
     }
 }
 ```

- **Return a Promise from Data Calls**: When calling a data service that returns a promise such as $http, return a promise in your calling function too.

 *Why?*: You can chain the promises together and take further action after the data call completes and resolves or rejects the promise.

 ```javascript
 /* recommended */

 activate();

 function activate() {
     /**
      * Step 1
      * Ask the getAvengers function for the
      * avenger data and wait for the promise
      */
     return getAvengers().then(function() {
       /**
        * Step 4
        * Perform an action on resolve of final promise
        */
       logger.info('Activated Avengers View');
     });
 }

 function getAvengers() {
     /**
      * Step 2
      * Ask the data service for the data and wait
      * for the promise
      */
     return dataservice.getAvengers()
       .then(function (data) {
           /**
            * Step 3
            * set the data and resolve the promise
            */
           vm.avengers = data;
           return vm.avengers;
       });
 }
 ```

 **[Back to top](#table-of-contents)**

## Directives

- **Limit DOM Manipulation**: When manipulating the DOM directly, use a directive. If alternative ways can be used such as using CSS to set styles or the [animation services](https://docs.angularjs.org/api/ngAnimate), Angular templating, [`ngShow`](https://docs.angularjs.org/api/ng/directive/ngShow) or [`ngHide`](https://docs.angularjs.org/api/ng/directive/ngHide), then use those instead. For example, if the directive simply hides and shows, use ngHide/ngShow, but if the directive does more, combining hide and show inside a directive may improve performance as it reduces watchers.

 *Why?*: DOM manipulation can be difficult to test, debug, and there are often better ways (e.g. CSS, animations, templates)

- **Restrict to Elements and Attributes**: When creating a directive that makes sense as a standalone element, allow restrict `E` (custom element) and optionally restrict `A` (custom attribute). Generally, if it could be its own control, `E` is appropriate. General guideline is allow `EA` but lean towards implementing as an element when its standalone and as an attribute when it enhances its existing DOM element.

 *Why?*: It makes sense.

 *Why?*: While we can allow the directive to be used as a class, if the directive is truly acting as an element it makes more sense as an element or at least as an attribute.

 ```html
 <!-- avoid -->
 <div class="my-calendar-range"></div>
 ```

 ```javascript
 /* avoid */
 angular
     .module('dsWidgets')
     .directive('myCalendarRange', myCalendarRange);

 function myCalendarRange () {
     var directive = {
         link: link,
         templateUrl: '/template/is/located/here.html',
         restrict: 'C'
     };
     return directive;

     function link(scope, element, attrs) {
       /* */
     }
 }
 ```

 ```html
 <!-- recommended -->
 <my-calendar-range></my-calendar-range>
 <div my-calendar-range></div>
 ```

 ```javascript
 /* recommended */
 angular
     .module('dsWidgets')
     .directive('myCalendarRange', myCalendarRange);

 function myCalendarRange () {
     var directive = {
         link: link,
         templateUrl: '/template/is/located/here.html',
         restrict: 'EA'
     };
     return directive;

     function link(scope, element, attrs) {
       /* */
     }
 }
 ```

- **Directives and ControllerAs**: Use `controller as` syntax with a directive to be consistent with using `controller as` with view and controller pairings.

 *Why?*: It makes sense and it's not difficult.

 - Note: The directive below demonstrates some of the ways you can use scope inside of link and directive controllers, using controllerAs. I inlined the template just to keep it all in one place.

 ```html
 <div my-example max="77"></div>
 ```

 ```javascript
 angular
     .module('app')
     .directive('myExample', myExample);

 function myExample() {
     var directive = {
         restrict: 'EA',
         // TODO: Use a templateUrl instead
         template: '<div>hello world</div> \
                     <div>max={{exVm.max}} \
                         <input ng-model="exVm.max"/> \
                     </div> \
                     <div>min={{exVm.min}} \
                         <input ng-model="exVm.min"/> \
                     </div> \
         ',
         scope: {
             max: '='
         },
         link: linkFunc,
         controller : DirCtrl,
         controllerAs: 'exVm'
     };
     return directive;

     /* @ngInject */
     function DirCtrl($scope) {
         // Injecting $scope just for comparison
         /* jshint validthis:true */
         var exVm = this;

         exVm.min = 3;
         exVm.max = $scope.max;
         console.log('CTRL: $scope.max = %i', $scope.max);
         console.log('CTRL: exVm.min = %i', exVm.min);
         console.log('CTRL: exVm.max = %i', exVm.max);
     }

     /* @ngInject */
     function linkFunc(scope, el, attr, ctrl) {
         console.log('LINK: scope.max = %i', scope.max);
         console.log('LINK: scope.exVm.min = %i', scope.exVm.min);
         console.log('LINK: scope.exVm.max = %i', scope.exVm.max);
     }
 }
 ```

**[Back to top](#table-of-contents)**

## Resolving Promises for a Controller

- **Controller Activation Promises**: Resolve start-up logic for a controller in an `activate` function.

 *Why?*: Placing start-up logic in a consistent place in the controller makes it easier to locate, more consistent to test, and helps avoid spreading out the activation logic across the controller.

 - Note: If you need to conditionally cancel the route before you start use the controller, use a route resolve instead.

 ```javascript
 /* avoid */
 function Avengers(dataservice) {
     var vm = this;
     vm.avengers = [];
     vm.title = 'Avengers';

     dataservice.getAvengers().then(function(data) {
         vm.avengers = data;
         return vm.avengers;
     });
 }
 ```

 ```javascript
 /* recommended */
 function Avengers(dataservice) {
     var vm = this;
     vm.avengers = [];
     vm.title = 'Avengers';

     activate();

     ////////////

     function activate() {
         return dataservice.getAvengers().then(function(data) {
             vm.avengers = data;
             return vm.avengers;
         });
     }
 }
 ```

- **Route Resolve Promises**: When a controller depends on a promise to be resolved, resolve those dependencies in the `$routeProvider` before the controller logic is executed. If you need to conditionally cancel a route before the controller is activated, use a route resolver.

 *Why?*: A controller may require data before it loads. That data may come from a promise via a custom factory or [$http](https://docs.angularjs.org/api/ng/service/$http). Using a [route resolve](https://docs.angularjs.org/api/ngRoute/provider/$routeProvider) allows the promise to resolve before the controller logic executes, so it might take action based on that data from the promise.

 ```javascript
 /* avoid */
 angular
   .module('app')
   .controller('Avengers', Avengers);

 function Avengers (movieService) {
   var vm = this;
   // unresolved
   vm.movies;
   // resolved asynchronously
   movieService.getMovies().then(function (response) {
     vm.movies = response.movies;
   });
 }
 ```

 ```javascript
 /* better */

 // route-config.js
 angular
   .module('app')
   .config(config);

 function config ($routeProvider) {
   $routeProvider
     .when('/avengers', {
       templateUrl: 'avengers.html',
       controller: 'Avengers',
       controllerAs: 'vm',
       resolve: {
         moviesPrepService: function (movieService) {
             return movieService.getMovies();
         }
       }
     });
 }

 // avengers.js
 angular
   .module('app')
   .controller('Avengers', Avengers);

 function Avengers (moviesPrepService) {
   var vm = this;
   vm.movies = moviesPrepService.movies;
 }

 ```

 - Note: The code example's dependency on `movieService` is not minification safe on its own. For details on how to make this code minification safe, see the sections on [dependency injection](#manual-dependency-injection) and on [minification and annotation](#minification-and-annotation).

**[Back to top](#table-of-contents)**

## Manual Dependency Injection

- **UnSafe from Minification**: Avoid using the shortcut syntax of declaring dependencies without using a minification-safe approach.

   *Why?*: The parameters to the component (e.g. controller, factory, etc) will be converted to mangled variables. For example, `common` and `dataservice` may become `a` or `b` and not be found by AngularJS.

 ```javascript
 /* avoid - not minification-safe*/
 angular
   .module('app')
   .controller('Dashboard', Dashboard);

 function Dashboard(common, dataservice) {
 }
 ```

 - This code may produce mangled variables when minified and thus cause runtime errors.

 ```javascript
 /* avoid - not minification-safe*/
 angular.module('app').controller('Dashboard', d);function d(a, b) { }
 ```

- **Manually Identify Dependencies**: Use $inject to manually identify your dependencies for AngularJS components.

   *Why?*: This technique mirrors the technique used by [`ng-annotate`](https://github.com/olov/ng-annotate), which I recommend for automating the creation of minification safe dependencies. If `ng-annotate` detects injection has already been made, it will not duplicate it.

   *Why?*: This safeguards your dependencies from being vulnerable to minification issues when parameters may be mangled. For example, `common` and `dataservice` may become `a` or `b` and not be found by AngularJS.

   *Why?*: Avoid creating in-line dependencies as long lists can be difficult to read in the array. Also it can be confusing that the array is a series of strings while the last item is the component's function.

 ```javascript
 /* avoid */
 angular
   .module('app')
   .controller('Dashboard',
     ['$location', '$routeParams', 'common', 'dataservice', Dashboard]);

 function Dashboard($location, $routeParams, common, dataservice) {
 }
 ```

 ```javascript
 /* recommended */
 angular
   .module('app')
   .controller('Dashboard', Dashboard);

 Dashboard.$inject = ['$location', '$routeParams', 'common', 'dataservice'];

 function Dashboard($location, $routeParams, common, dataservice) {
 }
 ```

 - Note: When your function is below a return statement the $inject may be unreachable (this may happen in a directive). You can solve this by either moving the $inject above the return statement or by using the alternate array injection syntax.

 - Note: [`ng-annotate 0.10.0`](https://github.com/olov/ng-annotate) introduced a feature where it moves the `$inject` to where it is reachable.

 ```javascript
 // inside a directive definition
 function outer() {
   return {
       controller: DashboardPanel,
   };

   DashboardPanel.$inject = ['logger']; // Unreachable
   function DashboardPanel(logger) {
   }
 }
 ```

 ```javascript
 // inside a directive definition
 function outer() {
   DashboardPanel.$inject = ['logger']; // reachable
   return {
       controller: DashboardPanel,
   };

   function DashboardPanel(logger) {
   }
 }
 ```

- **Manually Identify Route Resolver Dependencies**: Use $inject to manually identify your route resolver dependencies for AngularJS components.

   *Why?*: This technique breaks out the anonymous function for the route resolver, making it easier to read.

   *Why?*: An `$inject` statement can easily procede the resolver to handle making any dependencies minification safe.

 ```javascript
 /* recommended */
 function config ($routeProvider) {
   $routeProvider
     .when('/avengers', {
       templateUrl: 'avengers.html',
       controller: 'Avengers',
       controllerAs: 'vm',
       resolve: {
         moviesPrepService: moviePrepService
       }
     });
 }

 moviePrepService.$inject =  ['movieService'];
 function moviePrepService(movieService) {
     return movieService.getMovies();
 }
 ```

**[Back to top](#table-of-contents)**

## Minification and Annotation

- **ng-annotate**: Use [ng-annotate](//github.com/olov/ng-annotate) and comment functions that need automated dependency injection using `/** @ngInject */`

   *Why?*: This safeguards your code from any dependencies that may not be using minification-safe practices.

 - The following code is not using minification safe dependencies.

 ```javascript
 angular
   .module('app')
   .controller('Avengers', Avengers);

 /* @ngInject */
 function Avengers (storageService, avengerService) {
   var vm = this;
   vm.heroSearch = '';
   vm.storeHero = storeHero;

   function storeHero(){
     var hero = avengerService.find(vm.heroSearch);
     storageService.save(hero.name, hero);
   }
 }
 ```

 - When the above code is run through ng-annotate it will produce the following output with the `$inject` annotation and become minification-safe.

 ```javascript
 angular
   .module('app')
   .controller('Avengers', Avengers);

 /* @ngInject */
 function Avengers (storageService, avengerService) {
   var vm = this;
   vm.heroSearch = '';
   vm.storeHero = storeHero;

   function storeHero(){
     var hero = avengerService.find(vm.heroSearch);
     storageService.save(hero.name, hero);
   }
 }

 Avengers.$inject = ['storageService', 'avengerService'];
 ```

 - Note: If `ng-annotate` detects injection has already been made (e.g. `@ngInject` was detected), it will not duplicate the `$inject` code.

 - When using a route resolver you can prefix the resolver's function with `/* @ngInject */` and it will produce properly annotated code, keeping any injected dependencies minification safe.

 ```javascript
 // Using @ngInject annotations
 function config ($routeProvider) {
   $routeProvider
     .when('/avengers', {
       templateUrl: 'avengers.html',
       controller: 'Avengers',
       controllerAs: 'vm',
       resolve: { /* @ngInject */
         moviesPrepService: function (movieService) {
             return movieService.getMovies();
         }
       }
     });
 }
 ```

 - Note: Starting from AngularJS 1.3 use the [`ngApp`](https://docs.angularjs.org/api/ng/directive/ngApp) directive's `ngStrictDi` parameter. When present the injector will be created in "strict-di" mode causing the application to fail to invoke functions which do not use explicit function annotation (these may not be minification safe). Debugging info will be logged to the console to help track down the offending code.
 `<body ng-app="APP" ng-strict-di>`


**[Back to top](#table-of-contents)**

## Exception Handling

- **decorators**: Use a [decorator](https://docs.angularjs.org/api/auto/service/$provide#decorator), at config time using the [`$provide`](https://docs.angularjs.org/api/auto/service/$provide) service, on the [`$exceptionHandler`](https://docs.angularjs.org/api/ng/service/$exceptionHandler) service to perform custom actions when exceptions occur.

   *Why?*: Provides a consistent way to handle uncaught AngularJS exceptions for development-time or run-time.

 ```javascript
 /* recommended */
 angular
     .module('blocks.exception')
     .config(exceptionConfig);

 exceptionConfig.$inject = ['$provide'];

 function exceptionConfig($provide) {
     $provide.decorator('$exceptionHandler', extendExceptionHandler);
 }

 extendExceptionHandler.$inject = ['$delegate', 'toastr'];

 function extendExceptionHandler($delegate, toastr) {
     return function (exception, cause) {
         $delegate(exception, cause);
         var errorData = {
           exception: exception,
           cause: cause
         };
         /**
          * Could add the error to a service's collection,
          * add errors to $rootScope, log errors to remote web server,
          * or log locally. Or throw hard. It is entirely up to you.
          * throw exception;
          */
         toastr.error(exception.msg, errorData);
     };
 }
 ```

- **Exception Catchers**: Create a factory that exposes an interface to catch and gracefully handle exceptions.

   *Why?*: Provides a consistent way to catch exceptions that may be thrown in your code (e.g. during XHR calls or promise failures).

 ```javascript
 /* recommended */
 angular
     .module('dsException')
     .factory('exception', exception);

 exception.$inject = ['logger'];

 function exception(logger) {
     var service = {
         catcher: catcher
     };
     return service;

     function catcher(message) {
         return function (reason) {
             logger.error(message, reason);
         };
     }
 }
 ```

- **Route Errors**: Handle and log all routing errors using [`$routeChangeError`](https://docs.angularjs.org/api/ngRoute/service/$route#$routeChangeError).

   *Why?*: Provides a consistent way handle all routing errors.

   *Why?*: Potentially provides a better user experience if a routing error occurs and you route them to a friendly screen with more details or  recovery options.

 ```javascript
 /* recommended */
 function handleRoutingErrors() {
     /**
      * Route cancellation:
      * On routing error, go to the dashboard.
      * Provide an exit clause if it tries to do it twice.
      */
     $rootScope.$on('$routeChangeError',
         function (event, current, previous, rejection) {
             var destination = (current && (current.title || current.name || current.loadedTemplateUrl)) ||
                 'unknown target';
             var msg = 'Error routing to ' + destination + '. ' + (rejection.msg || '');
             /**
              * Optionally log using a custom service or $log.
              * (Don't forget to inject custom service)
              */
             logger.warning(msg, [current]);
         }
     );
 }
 ```

**[Back to top](#table-of-contents)**

## Naming

- **Prefixes for naming abstractions**: we use prefixes to distingish between the source project of a module (not of controller, directive, service or similar - these should have unique descriptive names - if you can't think of one, it's probably doing too much!):
 - *ux* : for any modules that are part of the ux repo
 - *ds* : for any modules that are part of the death_star

 Never prefix things with *ng* as these are angular's built-ins.

   *Why?*: So you can quickly scan the DI definition and know where the modules come from.

- **Feature File Names**: Use consistent names for all components following a pattern that describes the component's feature then (optionally) its type. The pattern should be `feature_type.js`.

   *Why?*: Provides a consistent way to quickly identify components.

   *Why?*: Provides pattern matching for any automated tasks.

 ```javascript
 /**
  * recommended
  */

 // controllers
 avengers_controller.js
 avengers_controller_spec.js

 // modules/config
 logger_module.js
 logger_config.js

Back to top

Modularity

  • Many Small, Self Contained Modules: Create small modules that enapsulate one responsibility.

    Why?: Modular applications make it easy to plug and go as they allow the development teams to build vertical slices of the applications and roll out incrementally. This means we can plug in new features as we develop them.

  • Feature Areas are Modules: Create modules that represent feature areas, such as layout, reusable and shared services, dashboards, and app specific features (e.g. customers, admin, sales).

    Why?: Self contained modules can be added to the application will little or no friction.

    Why?: Sprints or iterations can focus on feature areas and turn them on at the end of the sprint or iteration.

    Why?: Separating feature areas into modules makes it easier to test the modules in isolation and reuse code.

  • Reusable Blocks are Modules: Create modules that represent reusable application blocks for common services such as exception handling, logging, diagnostics, security, and local data stashing.

    Why?: These types of features are needed in many applications, so by keeping them separated in their own modules they can be application generic and be reused across applications.

Module Structure

  • 1 feature per file: All content is 1 feature per file. Each controller, service, module, view is in its own file.

  • Folders-by-Feature Structure: Create folders named for the feature they represent.

  • Keep Rails File Naming: For consistency, keep the Rails naming convention of having the "_" as a separator.

    Why?: A developer can locate the code, identify what each file represents at a glance and there is no repetitive nor redundant names.

    Why?: Helps reduce the app from becoming cluttered through organizing the content.

    Why?: When there are a lot of files (10+) locating them is easier with a consistent folder structures and more difficult in flat structures.

    other_module/
    my_module/
        my_module_module.js
        my_module_config.js
        my_module_routes.js
        controllers/
            attendees_controller.js
        directives/
            calendar_directive.js
            pop_up_directive.js
        services/
            dataservice_service.js
            localstorage_service.js

Back to top

Angular $ Wrapper Services

  • $document and $window: Use $document and $window instead of document and window.

    Why?: These services are wrapped by Angular and more easily testable than using document and window in tests. This helps you avoid having to mock document and window yourself.

  • $timeout and $interval: Use $timeout and $interval instead of setTimeout and setInterval .

    Why?: These services are wrapped by Angular and more easily testable and handle AngularJS's digest cycle thus keeping data binding in sync.

Back to top

Comments

  • jsDoc: If planning to produce documentation, use jsDoc syntax to document function names, description, params and returns

    Why?: You can generate (and regenerate) documentation from your code, instead of writing it from scratch.

    Why?: Provides consistency using a common industry tool.

    angular
      .module('app')
      .factory('logger', logger);
    
    /**
     * @name logger
     * @desc Application wide logger
     */
    function logger ($log) {
      var service = {
        logError: logError
      };
      return service;
    
      ////////////
    
      /**
       * @name logError
       * @desc Logs errors
       * @param {String} msg Message to log
       * @returns {String}
       */
      function logError(msg) {
        var loggedMsg = 'Error: ' + msg;
        $log.error(loggedMsg);
        return loggedMsg;
      };
    }

Back to top

Constants

  • Vendor Globals: Create an AngularJS Constant for vendor libraries' global variables.

    Why?: Provides a way to inject vendor libraries that otherwise are globals. This improves code testability by allowing you to more easily know what the dependencies of your components are (avoids leaky abstractions). It also allows you to mock these dependencies, where it makes sense.

    // constants.js
    
    /* global toastr:false, moment:false */
    (function () {
        'use strict';
    
        angular
            .module('dsCore')
            .constant('toastr', toastr)
            .constant('moment', moment);
    })();

Back to top