styleguide

Angular 1 Style Guide

Angular Team Endorsed

Special thanks to Igor Minar, lead on the Angular team, for reviewing, contributing feedback, and entrusting me to shepherd this guide.

Purpose

Opinionated Angular style guide for teams by @john_papa

If you are looking for an opinionated style guide for syntax, conventions, and structuring Angular applications, then step right in. These styles are based on my development experience with Angular, presentations, Pluralsight training courses and working in teams.

The purpose of this style guide is to provide guidance on building Angular applications by showing the conventions I use and, more importantly, why I choose them.

If you like this guide, check out my Angular Patterns: Clean Code course at Pluralsight which is a companion to this guide.

Angular Patterns: Clean Code

Community Awesomeness and Credit

Never work in a vacuum. I find that the Angular community is an incredible group who are passionate about sharing experiences. As such, Angular expert Todd Motto and I have collaborated on many styles and conventions. We agree on most, and some we diverge. I encourage you to check out Todd’s guidelines to get a sense for his approach and how it compares.

Many of my styles have been from the many pair programming sessions Ward Bell and I have had. My friend Ward has certainly helped influence the ultimate evolution of this guide.

See the Styles in a Sample App

While this guide explains the what, why and how, I find it helpful to see them in practice. This guide is accompanied by a sample application that follows these styles and patterns. You can find the sample application (named modular) here in the modular folder. Feel free to grab it, clone it, or fork it. Instructions on running it are in its readme.

Translations

Translations of this Angular style guide are maintained by the community and can be found here.

Table of Contents

  1. Single Responsibility
  2. IIFE
  3. Modules
  4. Controllers
  5. Services
  6. Factories
  7. Data Services
  8. Directives
  9. Resolving Promises
  10. Manual Annotating for Dependency Injection
  11. Minification and Annotation
  12. Exception Handling
  13. Naming
  14. Application Structure LIFT Principle
  15. Application Structure
  16. Modularity
  17. Startup Logic
  18. Angular $ Wrapper Services
  19. Testing
  20. Animations
  21. Comments
  22. JSHint
  23. JSCS
  24. Constants
  25. File Templates and Snippets
  26. Yeoman Generator
  27. Routing
  28. Task Automation
  29. Filters
  30. Angular Docs

Single Responsibility

Rule of 1

[Style Y001]

Why?: One component per file promotes easier unit testing and mocking.

Why?: One component per file makes it far easier to read, maintain, and avoid collisions with teams in source control.

Why?: One component per file avoids hidden bugs that often arise when combining components in a file where they may share variables, create unwanted closures, or unwanted coupling with dependencies.

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 */

  // some.controller.js
  angular
      .module('app')
      .controller('SomeController', SomeController);

  function SomeController() { }
  /* recommended */

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

  function someFactory() { }

Back to top

Small Functions

[Style Y002]

Why?: Small functions are easier to test, especially when they do one thing and serve one purpose.

Why?: Small functions promote reuse.

Why?: Small functions are easier to read.

Why?: Small functions are easier to maintain.

Why?: Small functions help avoid hidden bugs that come with large functions that share variables with external scope, create unwanted closures, or unwanted coupling with dependencies.

Back to top

IIFE

JavaScript Scopes

[Style Y010]

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.

  /* 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() { }
  /**
   * 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() { }
  })();

Back to top

Modules

Avoid Naming Collisions

[Style Y020]

Why?: Unique names help avoid module name collisions. Separators help define modules and their submodule hierarchy. For example app may be your root module while app.dashboard and app.users may be modules that are used as dependencies of app.

Definitions (aka Setters)

[Style Y021]

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',
      'app.shared',
      'app.dashboard'
  ]);

Instead use the simple setter syntax.

  /* recommended */
  angular
      .module('app', [
          'ngAnimate',
          'ngRoute',
          'app.shared',
          'app.dashboard'
      ]);

Getters

[Style Y022]

Why?: This produces more readable code and avoids variable 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

[Style Y023]

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

  /* recommended */

  // to set a module
  angular.module('app', []);

  // to get a module
  angular.module('app');

Named vs Anonymous Functions

[Style Y024]

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

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

  // dashboard.js
  angular
      .module('app')
      .controller('DashboardController', DashboardController);

  function DashboardController() { }
  // logger.js
  angular
      .module('app')
      .factory('logger', logger);

  function logger() { }

Back to top

Controllers

controllerAs View Syntax

[Style Y030]

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="CustomerController">
      
  </div>
  <!-- recommended -->
  <div ng-controller="CustomerController as customer">
      
  </div>

controllerAs Controller Syntax

[Style Y031]

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 the method to a factory, and reference them from the controller. Consider using $scope in a controller only when needed. For example when publishing and subscribing events using $emit, $broadcast, or $on.

  /* avoid */
  function CustomerController($scope) {
      $scope.name = {};
      $scope.sendMessage = function() { };
  }
  /* recommended - but see next section */
  function CustomerController() {
      this.name = {};
      this.sendMessage = function() { };
  }

controllerAs with vm

[Style Y032]

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 CustomerController() {
      this.name = {};
      this.sendMessage = function() { };
  }
  /* recommended */
  function CustomerController() {
      var vm = this;
      vm.name = {};
      vm.sendMessage = function() { };
  }

Note: You can avoid any jshint warnings by placing the comment above the line of code. However it is not needed when the function is named using UpperCasing, as this convention means it is a constructor function, which is what a controller is in Angular.

  /* 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.)

  <input ng-model="vm.title"/>
  function SomeController($scope, $log) {
      var vm = this;
      vm.title = 'Some Title';

      $scope.$watch('vm.title', function(current, original) {
          $log.info('vm.title was %s', original);
          $log.info('vm.title is now %s', current);
      });
  }

Note: When working with larger codebases, using a more descriptive name can help ease cognitive overhead & searchability. Avoid overly verbose names that are cumbersome to type.

  <!-- avoid -->
  <input ng-model="customerProductItemVm.title">
  <!-- recommended -->
  <input ng-model="productVm.title">

Bindable Members Up Top

[Style Y033]
  /* avoid */
  function SessionsController() {
      var vm = this;

      vm.gotoSession = function() {
        /* ... */
      };
      vm.refresh = function() {
        /* ... */
      };
      vm.search = function() {
        /* ... */
      };
      vm.sessions = [];
      vm.title = 'Sessions';
  }
  /* recommended */
  function SessionsController() {
      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 SessionsController(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 SessionsController(sessionDataService) {
      var vm = this;

      vm.gotoSession = gotoSession;
      vm.refresh = sessionDataService.refresh; // 1 liner is OK
      vm.search = search;
      vm.sessions = [];
      vm.title = 'Sessions';
  }

Function Declarations to Hide Implementation Details

[Style Y034]
  /**
   * avoid
   * Using function expressions.
   */
  function AvengersController(avengersService, 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 avengersService.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 AvengersController(avengersService, 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 avengersService.getAvengers().then(function(data) {
              vm.avengers = data;
              return vm.avengers;
          });
      }
  }

Defer Controller Logic to Services

[Style Y035]

  /* avoid */
  function OrderController($http, $q, config, userInfo) {
      var vm = this;
      vm.checkCredit = checkCredit;
      vm.isCreditOk;
      vm.total = 0;

      function checkCredit() {
          var settings = {};
          // Get the credit service base URL from config
          // Set credit service required headers
          // Prepare URL query string or data object with request data
          // Add user-identifying info so service gets the right credit limit for this user.
          // Use JSONP for this browser if it doesn't support CORS
          return $http.get(settings)
              .then(function(data) {
               // Unpack JSON data in the response object
                 // to find maxRemainingAmount
                 vm.isCreditOk = vm.total <= maxRemainingAmount
              })
              .catch(function(error) {
                 // Interpret error
                 // Cope w/ timeout? retry? try alternate service?
                 // Re-reject with appropriate error for a user to see
              });
      };
  }
  /* recommended */
  function OrderController(creditService) {
      var vm = this;
      vm.checkCredit = checkCredit;
      vm.isCreditOk;
      vm.total = 0;

      function checkCredit() {
         return creditService.isOrderTotalOk(vm.total)
            .then(function(isOk) { vm.isCreditOk = isOk; })
            .catch(showError);
      };
  }

Keep Controllers Focused

[Style Y037]

Assigning Controllers

[Style Y038]
  /* 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'
          });
  }
  <!-- avengers.html -->
  <div ng-controller="AvengersController as vm">
  </div>
  /* recommended */

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

  function config($routeProvider) {
      $routeProvider
          .when('/avengers', {
              templateUrl: 'avengers.html',
              controller: 'Avengers',
              controllerAs: 'vm'
          });
  }
  <!-- avengers.html -->
  <div>
  </div>

Back to top

Services

Singletons

[Style Y040]
  // service
  angular
      .module('app')
      .service('logger', logger);

  function logger() {
    this.logError = function(msg) {
      /* */
    };
  }
  // factory
  angular
      .module('app')
      .factory('logger', logger);

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

Back to top

Factories

Single Responsibility

[Style Y050]

Singletons

[Style Y051]

Accessible Members Up Top

[Style Y052]
  /* avoid */
  function dataService() {
    var someValue = '';
    function save() {
      /* */
    };
    function validate() {
      /* */
    };

    return {
        save: save,
        someValue: someValue,
        validate: validate
    };
  }
  /* 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"

Function Declarations to Hide Implementation Details

[Style Y053]
  /**
   * 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;
  }
  /**
   * 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

Data Services

Separate Data Calls

[Style Y060]
  /* recommended */

  // dataservice factory
  angular
      .module('app.core')
      .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.

  /* recommended */

  // controller calling the dataservice factory
  angular
      .module('app.avengers')
      .controller('AvengersController', AvengersController);

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

  function AvengersController(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

[Style Y061]
  /* 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

Directives

Limit 1 Per File

[Style Y070]
  /* avoid */
  /* directives.js */

  angular
      .module('app.widgets')

      /* order directive that is specific to the order module */
      .directive('orderCalendarRange', orderCalendarRange)

      /* sales directive that can be used anywhere across the sales app */
      .directive('salesCustomerInfo', salesCustomerInfo)

      /* spinner directive that can be used anywhere across apps */
      .directive('sharedSpinner', sharedSpinner);

  function orderCalendarRange() {
      /* implementation details */
  }

  function salesCustomerInfo() {
      /* implementation details */
  }

  function sharedSpinner() {
      /* implementation details */
  }
  /* recommended */
  /* calendar-range.directive.js */

  /**
   * @desc order directive that is specific to the order module at a company named Acme
   * @example <div acme-order-calendar-range></div>
   */
  angular
      .module('sales.order')
      .directive('acmeOrderCalendarRange', orderCalendarRange);

  function orderCalendarRange() {
      /* implementation details */
  }
  /* recommended */
  /* customer-info.directive.js */

  /**
   * @desc sales directive that can be used anywhere across the sales app at a company named Acme
   * @example <div acme-sales-customer-info></div>
   */
  angular
      .module('sales.widgets')
      .directive('acmeSalesCustomerInfo', salesCustomerInfo);

  function salesCustomerInfo() {
      /* implementation details */
  }
  /* recommended */
  /* spinner.directive.js */

  /**
   * @desc spinner directive that can be used anywhere across apps at a company named Acme
   * @example <div acme-shared-spinner></div>
   */
  angular
      .module('shared.widgets')
      .directive('acmeSharedSpinner', sharedSpinner);

  function sharedSpinner() {
      /* implementation details */
  }

Note: There are many naming options for directives, especially since they can be used in narrow or wide scopes. Choose one that makes the directive and its file name distinct and clear. Some examples are below, but see the Naming section for more recommendations.

Manipulate DOM in a Directive

[Style Y072]

Provide a Unique Directive Prefix

[Style Y073]

Restrict to Elements and Attributes

[Style Y074]
  <!-- avoid -->
  <div class="my-calendar-range"></div>
  /* avoid */
  angular
      .module('app.widgets')
      .directive('myCalendarRange', myCalendarRange);

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

      function link(scope, element, attrs) {
        /* */
      }
  }
  <!-- recommended -->
  <my-calendar-range></my-calendar-range>
  <div my-calendar-range></div>
  /* recommended */
  angular
      .module('app.widgets')
      .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

[Style Y075]
  <div my-example max="77"></div>
  angular
      .module('app')
      .directive('myExample', myExample);

  function myExample() {
      var directive = {
          restrict: 'EA',
          templateUrl: 'app/feature/example.directive.html',
          scope: {
              max: '='
          },
          link: linkFunc,
          controller: ExampleController,
          // note: This would be 'ExampleController' (the exported controller name, as string)
          // if referring to a defined controller in its separate file.
          controllerAs: 'vm',
          bindToController: true // because the scope is isolated
      };

      return directive;

      function linkFunc(scope, el, attr, ctrl) {
          console.log('LINK: scope.min = %s *** should be undefined', scope.min);
          console.log('LINK: scope.max = %s *** should be undefined', scope.max);
          console.log('LINK: scope.vm.min = %s', scope.vm.min);
          console.log('LINK: scope.vm.max = %s', scope.vm.max);
      }
  }

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

  function ExampleController($scope) {
      // Injecting $scope just for comparison
      var vm = this;
      vm.min = 3;
      vm.$onInit = onInit;
      
      //////////
      
      console.log('CTRL: $scope.vm.min = %s', $scope.vm.min);
      console.log('CTRL: $scope.vm.max = %s', $scope.vm.max); // undefined in Angular 1.5+
      console.log('CTRL: vm.min = %s', vm.min);
      console.log('CTRL: vm.max = %s', vm.max); // undefined in Angular 1.5+
      
      // Angular 1.5+ does not bind attributes until calling $onInit();
      function onInit() {
          console.log('CTRL-onInit: $scope.vm.min = %s', $scope.vm.min);
          console.log('CTRL-onInit: $scope.vm.max = %s', $scope.vm.max);
          console.log('CTRL-onInit: vm.min = %s', vm.min);
          console.log('CTRL-onInit: vm.max = %s', vm.max);
      }
  }
  <!-- example.directive.html -->
  <div>hello world</div>
  <div>max=<input ng-model="vm.max"/></div>
  <div>min=<input ng-model="vm.min"/></div>

Note: You can also name the controller when you inject it into the link function and access directive attributes as properties of the controller.

  // Alternative to above example
  function linkFunc(scope, el, attr, vm) {
      console.log('LINK: scope.min = %s *** should be undefined', scope.min);
      console.log('LINK: scope.max = %s *** should be undefined', scope.max);
      console.log('LINK: vm.min = %s', vm.min);
      console.log('LINK: vm.max = %s', vm.max);
  }
[Style Y076]
  <div my-example max="77"></div>
  angular
      .module('app')
      .directive('myExample', myExample);

  function myExample() {
      var directive = {
          restrict: 'EA',
          templateUrl: 'app/feature/example.directive.html',
          scope: {
              max: '='
          },
          controller: ExampleController,
          controllerAs: 'vm',
          bindToController: true
      };

      return directive;
  }

  function ExampleController() {
      var vm = this;
      vm.min = 3;
      vm.$onInit = onInit;
      
      function onInit() = {
          console.log('CTRL: vm.min = %s', vm.min);
          console.log('CTRL: vm.max = %s', vm.max);
      }
  }
  <!-- example.directive.html -->
  <div>hello world</div>
  <div>max=<input ng-model="vm.max"/></div>
  <div>min=<input ng-model="vm.min"/></div>

Back to top

Resolving Promises

Controller Activation Promises

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

      dataservice.getAvengers().then(function(data) {
          vm.avengers = data;
          return vm.avengers;
      });
  }
  /* recommended */
  function AvengersController(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

[Style Y081]
  /* avoid */
  angular
      .module('app')
      .controller('AvengersController', AvengersController);

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

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

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

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

  AvengersController.$inject = ['moviesPrepService'];
  function AvengersController(moviesPrepService) {
      var vm = this;
      vm.movies = moviesPrepService.movies;
  }

Note: The example below shows the route resolve points to a named function, which is easier to debug and easier to handle dependency injection.

  /* even better */

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

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

  function moviesPrepService(movieService) {
      return movieService.getMovies();
  }

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

  AvengersController.$inject = ['moviesPrepService'];
  function AvengersController(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 and on minification and annotation.

Back to top

Handling Exceptions with Promises

[Style Y082]
  /* avoid */

  function getCustomer(id) {
      return $http.get('/api/customer/' + id)
          .then(getCustomerComplete)
          .catch(getCustomerFailed);

      function getCustomerComplete(data, status, headers, config) {
          return data.data;
      }

      function getCustomerFailed(e) {
          var newMessage = 'XHR Failed for getCustomer'
          if (e.data && e.data.description) {
            newMessage = newMessage + '\n' + e.data.description;
          }
          e.data.description = newMessage;
          logger.error(newMessage);
          // ***
          // Notice there is no return of the rejected promise
          // ***
      }
  }
  /* recommended */
  function getCustomer(id) {
      return $http.get('/api/customer/' + id)
          .then(getCustomerComplete)
          .catch(getCustomerFailed);

      function getCustomerComplete(data, status, headers, config) {
          return data.data;
      }

      function getCustomerFailed(e) {
          var newMessage = 'XHR Failed for getCustomer'
          if (e.data && e.data.description) {
            newMessage = newMessage + '\n' + e.data.description;
          }
          e.data.description = newMessage;
          logger.error(newMessage);
          return $q.reject(e);
      }
  }

Back to top

Manual Annotating for Dependency Injection

UnSafe from Minification

[Style Y090]

Manually Identify Dependencies

[Style Y091]

Manually Identify Route Resolver Dependencies

[Style Y092]

Back to top

Minification and Annotation

ng-annotate

[Style Y100]

Use Gulp or Grunt for ng-annotate

[Style Y101]

Back to top

Exception Handling

decorators

[Style Y110]

Exception Catchers

[Style Y111]

Route Errors

[Style Y112]

Back to top

Naming

Naming Guidelines

[Style Y120]

Feature File Names

[Style Y121]

Note: Another common convention is naming controller files without the word controller in the file name such as avengers.js instead of avengers.controller.js. All other conventions still hold using a suffix of the type. Controllers are the most common type of component so this just saves typing and is still easily identifiable. I recommend you choose 1 convention and be consistent for your team. My preference is avengers.controller.js identifying the AvengersController.

```javascript
/**
 * recommended
 */
// Controllers
avengers.js
avengers.spec.js
```

Test File Names

[Style Y122]

Controller Names

[Style Y123]

Controller Name Suffix

[Style Y124]

Factory and Service Names

[Style Y125]

Directive Component Names

[Style Y126]

Modules

[Style Y127]

Configuration

[Style Y128]

Routes

[Style Y129]

Back to top

Application Structure LIFT Principle

LIFT

[Style Y140]

Locate

[Style Y141]

Identify

[Style Y142]

Flat

[Style Y143]

T-DRY (Try to Stick to DRY)

[Style Y144]

Back to top

Application Structure

Overall Guidelines

[Style Y150]

Layout

[Style Y151]

Folders-by-Feature Structure

[Style Y152]

Back to top

Modularity

Many Small, Self Contained Modules

[Style Y160]

Create an App Module

[Style Y161]

Keep the App Module Thin

[Style Y162]

Feature Areas are Modules

[Style Y163]

Reusable Blocks are Modules

[Style Y164]

Module Dependencies

[Style Y165]

Back to top

Startup Logic

Configuration

[Style Y170]
  angular
      .module('app')
      .config(configure);

  configure.$inject =
      ['routerHelperProvider', 'exceptionHandlerProvider', 'toastr'];

  function configure (routerHelperProvider, exceptionHandlerProvider, toastr) {
      exceptionHandlerProvider.configure(config.appErrorPrefix);
      configureStateHelper();

      toastr.options.timeOut = 4000;
      toastr.options.positionClass = 'toast-bottom-right';

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

      function configureStateHelper() {
          routerHelperProvider.configure({
              docTitle: 'NG-Modular: '
          });
      }
  }

Run Blocks

[Style Y171]
  angular
      .module('app')
      .run(runBlock);

  runBlock.$inject = ['authenticator', 'translator'];

  function runBlock(authenticator, translator) {
      authenticator.initialize();
      translator.initialize();
  }

Back to top

Angular $ Wrapper Services

$document and $window

[Style Y180]

$timeout and $interval

[Style Y181]

Back to top

Testing

Unit testing helps maintain clean code, as such I included some of my recommendations for unit testing foundations with links for more information.

Write Tests with Stories

[Style Y190]

Testing Library

[Style Y191]

Test Runner

[Style Y192]

Stubbing and Spying

[Style Y193]

Headless Browser

[Style Y194]

Code Analysis

[Style Y195]

Alleviate Globals for JSHint Rules on Tests

[Style Y196]

Testing Tools

Organizing Tests

[Style Y197]

Back to top

Animations

Usage

[Style Y210]

Sub Second

[Style Y211]

animate.css

[Style Y212]

Back to top

Comments

jsDoc

[Style Y220]

Back to top

JS Hint

Use an Options File

[Style Y230]

Back to top

JSCS

Use an Options File

[Style Y235]

Back to top

Constants

Vendor Globals

[Style Y240]
[Style Y241]

Back to top

File Templates and Snippets

Use file templates or snippets to help follow consistent styles and patterns. Here are templates and/or snippets for some of the web development editors and IDEs.

Sublime Text

[Style Y250]

Visual Studio

[Style Y251]

WebStorm

[Style Y252]

Individual templates are also available for download within the webstorm-angular-live-templates folder

Atom

[Style Y253]

Brackets

[Style Y254]

vim

[Style Y255]

Visual Studio Code

[Style Y256]

Emacs

[Style Y257]

Back to top

Yeoman Generator

[Style Y260]

You can use the HotTowel yeoman generator to create an app that serves as a starting point for Angular that follows this style guide.

  1. Install generator-hottowel
  npm install -g generator-hottowel
  1. Create a new folder and change directory to it
  mkdir myapp
  cd myapp
  1. Run the generator
  yo hottowel helloWorld

Back to top

Routing

Client-side routing is important for creating a navigation flow between views and composing views that are made of many smaller templates and directives.

[Style Y270]
[Style Y271]

Back to top

Task Automation

Use Gulp or Grunt for creating automated tasks. Gulp leans to code over configuration while Grunt leans to configuration over code. I personally prefer Gulp as I feel it is easier to read and write, but both are excellent.

Learn more about gulp and patterns for task automation in my Gulp Pluralsight course

[Style Y400]

Back to top

Filters

[Style Y420]

Back to top

Angular docs

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

Back to top