Skip to content

turn/angularjs-styleguide

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 

Repository files navigation

AngularJS styleguide

The Turn AngularJS styleguide (based off this).

Table of Contents

  1. Folder structure
  2. Modules
  3. Controllers
  4. Services
  5. Directives
  6. Filters
  7. Routing resolves
  8. Publish and subscribe events
  9. Angular wrapper references
  10. Comment standards
  11. Minification and annotation

Folder structure

app/
|_module1/
  |_configs/
    |_module1RoutesConfig.js
  |_constants/
    |_module1Constant.js
  |_controllers/
    |_module1BarController.js
    |_module1FooController.js
  |_directives/
    |_module1Foo.js
  |_factories/
    |_module1FooFactory.js
  |_runs/
    |_module1Baz.js
  |_app.js
|_module2/
  |_...
  |_app.js
|_...
|_app.js
  • Avoid empty folders
  • All folder and file names should be in camelCase
  • Put all angular blocks (controllers, directives, services, etc.) in their own files and require() them from their module's app.js
  • Prefix all files with the module name to avoid name collisions when multiple modules are running within the same app
  • Suffix config, controller, factory, provider, run, and service file names with the type of angular construct (eg. Factory, Service, etc.)
  • Pieces of code that are shared between modules should live in their own repositories, and be installed and managed with bower. If a piece of code is too small or too specific to be separated, it should live in a shared/ folder (a sibling of your module folders)
  • Each module should have an app.js, which is its entry point.

Modules

  • Definitions: Declare modules without a variable using the setter and getter syntax

    // bad
    var app = angular.module('app', []);
    app.controller();
    app.factory();
    
    // good
    angular
      .module('app', [])
      .controller()
      .factory();
  • Note: Using angular.module('app', []); sets a module, whereas angular.module('app'); gets the module. Only set once and get for all other instances.

  • When creating modules for open source use, be sure to prefix the module name with "turn/"

  // bad
  angular
  .module('foo', [])
  ...

  // good
  angular
  .module('turn/foo', [])
  ...
  • Methods: Pass requires into module methods rather than assign as an inline callback

    // bad
    angular
      .module('app', [])
      .controller('MainCtrl', function () {
        ...
      })
      .service('SomeService', function () {
        ...
      });
    
    // good
    angular
      .module('app', [])
      .controller('MainCtrl', require('./controllers/foo'))
      .service('SomeService', require('./services/foo'));
  • This aids with readability and reduces the volume of code "wrapped" inside the Angular framework

Back to top

Controllers

  • Methods: When assigning methods or propeties to the scope, prefer extend syntax over direct assignment:
// bad
$scope.foo = 1;
$scope.bar = 2;
$scope.baz = function () { ... };

// good
angular.extend($scope, {
  foo: 1,
  bar: 2,
  baz: function () { ... }
})

angular 1.2+ only:

  • controllerAs syntax: Controllers are classes, so use the controllerAs syntax at all times

    <!-- bad -->
    <div ng-controller="MainCtrl">
      {{ someObject }}
    </div>
    
    <!-- good -->
    <div ng-controller="MainCtrl as main">
      {{ main.someObject }}
    </div>
  • In the DOM we get a variable per controller, which aids nested controller methods avoiding any $parent calls

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

    // bad
    function MainCtrl ($scope) {
      $scope.someObject = {};
      $scope.doSomething = function () {
    
      };
    }
    
    // good
    function MainCtrl () {
      this.someObject = {};
      this.doSomething = function () {
    
      };
    }
  • Only use $scope in controllerAs when necessary, for example publishing and subscribing events using $emit, $broadcast, $on or using $watch, try to limit the use of these, however and treat $scope uses as special

Back to top

Services

  • Prefer services over factories and providers.

    function SomeService () {
      this.someMethod = function () {
    
      };
    }

Back to top

Directives

  • Declaration restrictions: Only use custom element and custom attribute methods for declaring your Directives (restrict: 'EA') depending on the Directive's role

    <!-- bad -->
    <div class="my-directive"></div>
    
    <!-- good -->
    <my-directive></my-directive>
  • Comment and class name declarations are confusing and should be avoided.

  • Templating: Prefer external templates over inlined HTML. Template names should be the dash-cased equivalents of their directive names.

    // bad
    function someDirective () {
      return {
        template: '<div class="some-directive">' +
          '<h1>My directive</h1>' +
        '</div>'
      };
    }
    
    // good
    function someDirective () {
      return {
        templateUrl: './templates/some-directive.html'
      };
    }
  • DOM manipulation: Only takes place inside Directives, never a controller/service

    // bad
    function UploadCtrl () {
      $('.dragzone').on('dragend', function () {
        // handle drop functionality
      });
    }
    angular
      .module('app')
      .controller('UploadCtrl', UploadCtrl);
    
    // good
    function dragUpload () {
      return {
        restrict: 'EA',
        link: function (scope, element, attrs) {
          element.on('dragend', function () {
            // handle drop functionality
          });
        }
      };
    }
    angular
      .module('app')
      .directive('dragUpload', dragUpload);
  • Naming conventions: Never ng-* prefix custom directives, they might conflict future native directives.

    // bad
    // <div ng-upload></div>
    function ngUpload () {
      return {};
    }
    angular
      .module('app')
      .directive('ngUpload', ngUpload);
    
    // good
    // <div drag-upload></div>
    function dragUpload () {
      return {};
    }
    angular
      .module('app')
      .directive('dragUpload', dragUpload);
  • controllerAs: Use the controllerAs syntax inside Directives also (angular 1.2+ only)

    // bad
    function dragUpload () {
      return {
        controller: function ($scope) {
    
        }
      };
    }
    angular
      .module('app')
      .directive('dragUpload', dragUpload);
    
    // good
    function dragUpload () {
      return {
        controllerAs: 'dragUpload',
        controller: function () {
    
        }
      };
    }
    angular
      .module('app')
      .directive('dragUpload', dragUpload);

Back to top

Filters

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

    // bad
    function SomeCtrl () {
      this.startsWithLetterA = function (items) {
        return items.filter(function (item) {
          return /$a/i.test(item.name);
        });
      };
    }
    angular
      .module('app')
      .controller('SomeCtrl', SomeCtrl);
    
    // good
    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 dependencies of a Controller in the $routeProvider not the Controller itself

    // bad
    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);
    
    // good
    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

    // bad
    function MainCtrl (SomeService) {
      this.something = SomeService.something;
    }
    
    function config ($routeProvider) {
      $routeProvider
      .when('/', {
        templateUrl: 'views/main.html',
        controllerAs: 'main',
        controller: 'MainCtrl'
        resolve: {
          doSomething: function () {
            return SomeService.doSomething();
          }
        }
      });
    }
    
    // good
    function MainCtrl (SomeService) {
      this.something = SomeService.something;
    }
    
    MainCtrl.resolve = {
      doSomething: function () {
        return SomeService.doSomething();
      }
    };
    
    function config ($routeProvider) {
      $routeProvider
      .when('/', {
        templateUrl: 'views/main.html',
        controllerAs: 'main',
        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: event emitters should be avoided when possible (prefer $watches and 2-way binding instead). When necessary, 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: $rootScope.$on listeners are different than $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);

Back to top

Angular wrapper references

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

    // bad
    function dragUpload () {
      return {
        link: function (scope, element, attrs) {
          document.addEventListener('click', function () {
    
          });
        }
      };
    }
    
    // good
    function dragUpload () {
      return {
        link: function (scope, element, attrs, $document) {
          $document.addEventListener('click', function () {
    
          });
        }
      };
    }
  • $timeout and $interval: Use $timeout and $interval (angular 1.2+ only) over their native counterparts to automatically trigger digests when the timer fires

    // bad
    function dragUpload () {
      return {
        link: function (scope, element, attrs) {
          setTimeout(function () {
            scope.$apply(function(){
              ...
            });
          }, 1000);
        }
      };
    }
    
    // good
    function dragUpload () {
      return {
        link: function (scope, element, attrs, $timeout) {
          $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 and comment functions that need automated dependency injection using /* @ngInject */

    // controller.js
    module.exports = /* @ngInject */ function (SomeService) {
      this.doSomething = SomeService.doSomething;
    }
    
    // app.js
    angular
      .module('app')
      .controller('MainCtrl', require('./controllers/controller'));
  • Which ng-annotate compiles to the following annotated code

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

Back to top

Angular docs

For anything else, API reference, check the Angular documentation.

Contributing

Open an issue first to discuss potential changes/additions.

License

(The MIT License)

Copyright (c) 2014 Todd Motto

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

About

AngularJS styleguide for teams

Resources

Stars

3 stars

Watchers

17 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors