AngularJS StyleGuide

Filters

  • Global filters: Create global filters using angular.filter() only. Never use local filters inside Controllers/Services

    // avoid
    function SomeCtrl () {
      this.startsWithLetterA = function (items) {
        return items.filter(function (item) {
          return /^a/i.test(item.name);
        });
      };
    }
    angular
      .module('app')
      .controller('SomeCtrl', SomeCtrl);
    
    // recommended
    function startsWithLetterA () {
      return function (items) {
        return items.filter(function (item) {
          return /^a/i.test(item.name);
        });
      };
    }
    angular
      .module('app')
      .filter('startsWithLetterA', startsWithLetterA);
    
  • This enhances testing and reusability

Back to top

Routing resolves

  • Promises: Resolve Controller dependencies in the $routeProvider (or $stateProvider for ui-router), not the Controller itself

    // avoid
    function MainCtrl (SomeService) {
      var _this = this;
      // unresolved
      _this.something;
      // resolved asynchronously
      SomeService.doSomething().then(function (response) {
        _this.something = response;
      });
    }
    angular
      .module('app')
      .controller('MainCtrl', MainCtrl);
    
    // recommended
    function config ($routeProvider) {
      $routeProvider
      .when('/', {
        templateUrl: 'views/main.html',
        resolve: {
          // resolve here
        }
      });
    }
    angular
      .module('app')
      .config(config);
    
  • Controller.resolve property: Never bind logic to the router itself. Reference a resolve property for each Controller to couple the logic

    // avoid
    function MainCtrl (SomeService) {
      this.something = SomeService.something;
    }
    
    function config ($routeProvider) {
      $routeProvider
      .when('/', {
        templateUrl: 'views/main.html',
        controllerAs: 'vm',
        controller: 'MainCtrl'
        resolve: {
          doSomething: function () {
            return SomeService.doSomething();
          }
        }
      });
    }
    
    // recommended
    function MainCtrl (SomeService) {
      this.something = SomeService.something;
    }
    
    MainCtrl.resolve = {
      doSomething: function (SomeService) {
        return SomeService.doSomething();
      }
    };
    
    function config ($routeProvider) {
      $routeProvider
      .when('/', {
        templateUrl: 'views/main.html',
        controllerAs: 'vm',
        controller: 'MainCtrl'
        resolve: MainCtrl.resolve
      });
    }
    
  • This keeps resolve dependencies inside the same file as the Controller and the router free from logic

Back to top

Publish and subscribe events

  • $scope: Use the $emit and $broadcast methods to trigger events to direct relationship scopes only

    // up the $scope
    $scope.$emit('customEvent', data);
    
    // down the $scope
    $scope.$broadcast('customEvent', data);
    
  • $rootScope: Use only $emit as an application-wide event bus and remember to unbind listeners

    // all $rootScope.$on listeners
    $rootScope.$emit('customEvent', data);
    
  • Hint: Because the $rootScope is never destroyed, $rootScope.$on listeners aren't either, unlike $scope.$on listeners and will always persist, so they need destroying when the relevant $scope fires the $destroy event

    // call the closure
    var unbind = $rootScope.$on('customEvent'[, callback]);
    $scope.$on('$destroy', unbind);
    
  • For multiple $rootScope listeners, use an Object literal and loop each one on the $destroy event to unbind all automatically

    var rootListeners = {
      'customEvent1': $rootScope.$on('customEvent1'[, callback]),
      'customEvent2': $rootScope.$on('customEvent2'[, callback]),
      'customEvent3': $rootScope.$on('customEvent3'[, callback])
    };
    for (var unbind in rootListeners) {
      $scope.$on('$destroy', rootListeners[unbind]);
    }
    

Back to top

Performance

  • One-time binding syntax: In newer versions of Angular (v1.3.0-beta.10+), use the one-time binding syntax {{ ::value }} where it makes sense

    // avoid
    <h1>{{ vm.title }}</h1>
    
    // recommended
    <h1>{{ ::vm.title }}</h1>
    

    Why? : Binding once removes the watcher from the scope's $$watchers array after the undefined variable becomes resolved, thus improving performance in each dirty-check

  • Consider $scope.$digest: Use $scope.$digest over $scope.$apply where it makes sense. Only child scopes will update

    $scope.$digest();
    

    Why? : $scope.$apply will call $rootScope.$digest, which causes the entire application $$watchers to dirty-check again. Using $scope.$digest will dirty check current and child scopes from the initiated $scope

Back to top

Angular wrapper references

  • $document and $window: Use $document and $window at all times to aid testing and Angular references

    // avoid
    function dragUpload () {
      return {
        link: function ($scope, $element, $attrs) {
          document.addEventListener('click', function () {
    
          });
        }
      };
    }
    
    // recommended
    function dragUpload () {
      return {
        link: function ($scope, $element, $attrs, $document) {
          $document.addEventListener('click', function () {
    
          });
        }
      };
    }
    
  • $timeout and $interval: Use $timeout and $interval over their native counterparts to keep Angular's two-way data binding up to date

    // avoid
    function dragUpload () {
      return {
        link: function ($scope, $element, $attrs) {
          setTimeout(function () {
            //
          }, 1000);
        }
      };
    }
    
    // recommended
    function dragUpload ($timeout) {
      return {
        link: function ($scope, $element, $attrs) {
          $timeout(function () {
            //
          }, 1000);
        }
      };
    }
    

Back to top

Comment standards

  • jsDoc: Use jsDoc syntax to document function names, description, params and returns

    /**
     * @name SomeService
     * @desc Main application Controller
     */
    function SomeService (SomeService) {
    
      /**
       * @name doSomething
       * @desc Does something awesome
       * @param {Number} x - First number to do something with
       * @param {Number} y - Second number to do something with
       * @returns {Number}
       */
      this.doSomething = function (x, y) {
        return x * y;
      };
    
    }
    angular
      .module('app')
      .service('SomeService', SomeService);
    

Back to top

Minification and annotation

  • ng-annotate: Use ng-annotate for Gulp as ng-min is deprecated, and comment functions that need automated dependency injection using /** @ngInject */

    /**
     * @ngInject
     */
    function MainCtrl (SomeService) {
      this.doSomething = SomeService.doSomething;
    }
    angular
      .module('app')
      .controller('MainCtrl', MainCtrl);
    
  • Which produces the following output with the $inject annotation

    /**
     * @ngInject
     */
    function MainCtrl (SomeService) {
      this.doSomething = SomeService.doSomething;
    }
    MainCtrl.$inject = ['SomeService'];
    angular
      .module('app')
      .controller('MainCtrl', MainCtrl);