Skip to content

V1.1: AngularJS Style Guide: A takeoff on John Papa's excellent guide. This is slanted for more of a opinionated MVC discipline. Thanks to rich input from many AgileThought brainiacs!

License

Notifications You must be signed in to change notification settings

mbcooper/angularjs-styleguide

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 

Repository files navigation

AngularJS Style Guide Extensions

Opinionated MVC AngularJS style extensions / modifications @mbcoop

This is a commentary and extension of John Papa's Angular Starter. It makes a few modifications based on our experience build Enterprise AngularJS apps in the past 2 years.

*I note a % beside each rule on how strictly my guide agrees with Papa's.

Table of Contents

  1. Single Responsibility
  2. IIFE
  3. Modules
  4. Controllers
  5. Services
  6. Factories
  7. Data Services
  8. Directives
  9. Models
  10. JavaScript Patterns by Type
  11. Resolving Promises for a Controller
  12. Manual Annotating for Dependency Injection
  13. Minification and Annotation
  14. Exception Handling
  15. Naming
  16. Application Structure LIFT Principle
  17. Application Structure
  18. Modularity
  19. Startup Logic
  20. Angular $ Wrapper Services
  21. Testing
  22. Animations
  23. Comments
  24. JSHint
  25. File Templates and Snippets
  26. AngularJS Docs
  27. Contributing
  28. License

Single Responsibility

Rule of 1 (100%)

[Style Y001]
  • 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

JavaScript Closures

[Style Y010]
  • 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.

  • Automate IIFE using grunt or gulp.

Why?: Productivity. This ensures we always do it, and allows tests to poke inside of modules if desired.

/* 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() { }
})();
  • Note: For brevity only, the rest of the examples in this guide may omit the IIFE syntax. (And we automate the task anyway).

  • Note: IIFE's prevent test code from reaching private members like regular expressions or helper functions which are often good to unit test directly on their own. However you can test these through accessible members or by exposing them through their own component. For example placing helper functions, regular expressions or constants in their own factory or constant.

Back to top

Modules

Avoid Naming Collisions (90%)

[Style Y020]
  • Use unique naming conventions with separators for sub-modules.

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.

Twist: Our corporate accounts tend to namespace their C-sharp code like:

``` c#
// pattern : using <corp>|<app>.<layer>.<feature>.<class>
// example
    using myCorp.web.controller.users
    // or
    using userWeb.controller.users
```

In Angular projects, we attempt to follow a similar pattern, using an actual app-name, rather than app.

Definitions (aka Setters) (100%)

[Style Y021]
  • 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',
    'app.shared',
    'app.dashboard'
]);
Instead use the simple setter syntax.
/* recommended */
angular
  	.module('app', [
        'ngAnimate',
        'ngRoute',
        'app.shared',
        'app.dashboard'
    ]);

Getters (100%)

[Style Y022]
  • When using a module, avoid using a variable and instead use chaining with the getter syntax.

    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 (100%)

[Style Y023]
  • 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 (100%)

[Style Y024]
  • 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 (100% ++)

[Style Y030]
  • 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

[Style Y031]
  • 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

[Style Y032]
  • 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. 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);
    });
}

Bindable Members Up Top (80%)

[Style Y033]
  • 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"](https://raw.githubusercontent.com/johnpapa/angularjs-styleguide/master/assets/above-the-fold-1.png)

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';

@mbcooper: In general, we would want to bind to a known model, or view-model, rather than sprinkling the controller with new variables. The items you want to keep in the controller should be state-related information, and DOM-only bindings, like search terms, etc. Otherwise, push your logic and binding to models.

Function Declarations to Hide Implementation Details (100%)

[Style Y034]
  • 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 (90%)

[Style Y035]
  • Defer logic in a controller by delegating to services, models 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 or model 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 implementation details from the controller.

/* avoid */
function Order($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 Order(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(showServiceError);
    };
}

Keep Controllers Focused (110%!)

[Style Y037]
  • Define a controller for a view, and try not to reuse the controller for other views. Instead, move reusable logic to factories and keep the controller simple and focused on its view.

    Why?: Reusing controllers with several views is brittle and good end to end (e2e) test coverage is required to ensure stability across large applications.

Assigning Controllers

[Style Y038]
  • 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'
         });
 }
<!-- avengers.html -->
<div ng-controller="Avengers 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 (50%)

[Style Y040]
  • Services are instantiated with the new keyword, use this for public methods and variables. Alternatively, use the revealing module pattern. Use the .service module type.

    Note: All AngularJS services are singletons. This means that there is only one instance of a given service per injector.

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

function logger() {
  this.logError = function(msg) {
    /* */
  };
}
// service - revealing module pattern
angular
    .module('app')
    .service('logger', logger);


   var logError = function(msg) {
    /* */
 
}
  return {
  	logError: logError
  }

Back to top

Factories (90%)

Single Responsibility

[Style Y050]
  • Factories should have a single responsibility, that is encapsulated by its context. Once a factory begins to exceed that singular purpose, a new factory should be created.

Singletons

[Style Y051]
  • Factories are singletons and manufacture an object that contains the members of the model.

Note: the factory is a singleton, but we use it to create an object that we create with new. See models.

Accessible Members Up Top

[Style Y052]
  • Expose the callable members of the service (it's interface) at the top, using a technique derived from the Revealing Module Pattern.

    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.

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

Function Declarations to Hide Implementation Details (100%)

[Style Y053]
  • Use function declarations to hide implementation details. Keep your accessible members of the factory up top. Point those to function declarations that appears later in the file. For more details see this post.

    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

/**
 * 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 (80%, includes Papa promise)

[Style Y060]
  • Refactor logic for making data operations and interacting with data to a service. 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.

  • Wrap $http calls in $q. Return the promise.

    Why? We get more fine-grained control over errors. For example, we can return customized error objects, etc. if we capture success and failure. By returning the promise, we ensure the calling controller will have the proper model on success, and can react to promise conditions with UI blockers, flags etc. to make the experience better for the user.

  • Transform`responses in the success function

    Why? Again, fine-grained control. We ensure that the service returns a Model of the expected type, all prepped for us in our controller.

/* recommended */

// dataservice factory
angular
    .module('app.core', 
    ['$http', 'logger', '$q', 'AvengerModel'])
    .service('dataservice', dataservice);

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

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

        function getAvengersComplete(response) {
            var avengerModel = new AvengerModel(response); 
            defer.resolve(avengerModel);
        }

        function getAvengersFailed(error) {
            logger.error('XHR Failed for getAvengers.' + error.data);
            defer.reject();
        }
  	
  	return defer.promise();
    }
}
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('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;
            });
    }
}      
**[Back to top](#table-of-contents)**

Directives

Limit 1 Per File (100%)

[Style Y070]
  • Create one directive per file. Name the file for the directive.

    Why?: It is easy to mash all the directives in one file, but difficult to then break those out so some are shared across apps, some across modules, some just for one module.

    Why?: One directive per file is easy to maintain.

/* 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 */
/* calendarRange.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 */
/* customerInfo.directive.js */

/**
 * @desc spinner 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 it's file name distinct and clear. Some examples are below, but see the naming section for more recommendations.

Manipulate DOM in a Directive (100%)

[Style Y072]
  • 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, Angular templating, ngShow or ngHide, then use those instead. For example, if the directive simply hides and shows, use ngHide/ngShow.

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

Provide a Unique Directive Prefix (100%)

[Style Y073]
  • Provide a short, unique and descriptive directive prefix such as acmeSalesCustomerInfo which is declared in HTML as acme-sales-customer-info.

    Why?: The unique short prefix identifies the directive's context and origin. For example a prefix of cc- may indicate that the directive is part of a CodeCamper app while acme- may indicate a directive for the Acme company.

    Note: Avoid ng- as these are reserved for AngularJS directives.Research widely used directives to avoid naming conflicts, such as ion- for the Ionic Framework.

Restrict to Elements and Attributes (100%)

[Style Y074]
  • 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.

    Note: EA is the default for AngularJS 1.3 +

<!-- 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 (100%)

[Style Y075]
  • 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 in-lined the template just to keep it all in one place.

    Note: Regarding dependency injection, see Manually Identify Dependencies.

    Note: Note that the directive's controller is outside the directive's closure. This style eliminates issues where the injection gets created as unreachable code after a return.

<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,
        controllerAs: 'vm'
    };
    
    return directive;

    function linkFunc(scope, el, attr, ctrl) {
        console.log('LINK: scope.max = %i', scope.max);
        console.log('LINK: scope.vm.min = %i', scope.vm.min);
        console.log('LINK: scope.vm.max = %i', scope.vm.max);
    }
}

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

function ExampleController($scope) {
    // Injecting $scope just for comparison
    var vm = this;

    vm.min = 3; 
    vm.max = $scope.max; 
    console.log('CTRL: $scope.max = %i', $scope.max);
    console.log('CTRL: vm.min = %i', vm.min);
    console.log('CTRL: vm.max = %i', vm.max);
}
/* example.directive.html */
<div>hello world</div>
<div>max={{vm.max}}<input ng-model="vm.max"/></div>
<div>min={{vm.min}}<input ng-model="vm.min"/></div>

Back to top

Models

Use Factories to Create View Model Versions of Domain Objects

[Style C500]
  • Create a factory to generate instances of domain model objects for use in the UI. Create to/from DTO methods (if applicable) to translate from / to the server versions.

    Why? Creating named instances of our view models (as opposed to accepting un-named objects from services) makes it easier to debug, and separates persistence logic (services) from core UI-domain logic.

  • Use the revealing prototype pattern for models. Place all properties in the constructor (except those defined in Object.define .. ). Place all method in prototype extensions.

    Why? For objects that may have many instances, this is the most memory efficient, and cleanly separates methods and properties.

  • Place all model-related logic in that factory. Reduce logic in services and controllers on favor of models and view models.

Why? Allows us the focus the controllers on UI and services on persistence. A SOLID practice.

  // sample factory for Animals model in our Zoo app
  // tagging as Class will help intellisense in IntelliJ / Webstorm
  /**
  * @class Animal
  * /
  angular.module('zoo', [])
  	.factory('Animal', function(){

  		var Animal = function(){
  			this.name = '';
  			this.topSpeed = 0; // mph
  			this.weight = 0;
  			this.color = '';
  		};

  	/**
  	* @method distanceCovered - returns miles covered in
  	* @param {number} time - minutes
  	* @return {number} miles covered
  	Animal.prototype.distanceCovered = function(time){
  			var vm = this;
  			return (time * vm.topSpeed) / 60;
  	};
  	return Animal;
  });

##Patterns

Preferred JavaScript Patterns

  • Follow theses JavaScript patterns for each of these types of modules.

Why? It encourages common practices, and these patterns are easier to test.

Module Type Purpose Typical Instance JavaScript Pattern
Controllers View Controller Singleton Module Pattern
Controllers Directive Controller Singleton Module Pattern
Factory Models, View Models Multiple Instances Revealing Prototype Pattern
Services Persistence Management Singleton Revealing Module Pattern

Resolving Promises for a Controller

Controller Activation Promises (100%)

[Style Y080]
  • 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.

    Why?: The controller activate makes it convenient to re-use the logic for a refresh for the controller/View, keeps the logic together, gets the user to the View faster, makes animations easy on the ng-view or ui-view, and feels snappier to the user.

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

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

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

[Style Y081]
  • Use ui.router for state and location management. Why it's the future. It allows us to navigate by state, rather than location, has nice DOM options that allow state-linking and view locations, and allows very flexible nested views and states. Awesome.

  • When a controller depends on a promise to be resolved before the controller is activated, resolve those dependencies in the $stateProvider before the controller logic is executed. If you need to conditionally cancel a route before the controller is activated, use a route resolver. TODO!

  • Use a route resolve when you want to decide to cancel the route before ever transitioning to the View. TODO!

    Why?: A controller may require data before it loads. That data may come from a promise via a custom factory or $http. Using a route resolve allows the promise to resolve before the controller logic executes, so it might take action based on that data from the promise.

    Why?: The code executes after the route and in the controller’s activate function. The View starts to load right away. Data binding kicks in when the activate promise resolves. A “busy” animation can be shown during the view transition (via ng-view or ui-view)

    Note: The code executes before the route via a promise. Rejecting the promise cancels the route. Resolve makes the new view wait for the route to resolve. A “busy” animation can be shown before the resolve and through the view transition. If you want to get to the View faster and do not require a checkpoint to decide if you can get to the View, consider the controller activate technique instead.

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

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

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

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

Avengers.$inject = ['moviesPrepService'];
function Avengers(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($stateProvider) {
    $stateProvider
        .state(
            'avengerState', 
            {
  	          url:  '/avengers',
                templateUrl: 'avengers.html',
                controller: 'Avengers',
                controllerAs: 'vm',
                resolve: {
                    moviesPrepService: moviesPrepService
            }
        });
}

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

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

Avengers.$inject = ['moviesPrepService'];
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-annotating-for-dependency-injection) and on [minification and annotation](#minification-and-annotation).

Back to top

Annotating for Dependency Injection (Simplified from Papa)

UnSafe from Minification (100%)

[Style Y090]
  • 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.

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

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

Use ng-annotate (simplified)

[Style C091]

See C100

Back to top

Minification and Annotation

ng-annotate

[Style C100]
  • Use ng-annotate for Gulp or Grunt. This stack is mature enough to alleviate the need to manually identify dependencies, beyond listing them in a module definition.

  • Comment functions that need automated dependency injection using /** @ngInject */where they are not picked up by ng-annotate. In my experience, this has yet to be required.

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

    I too prefer Gulp as I feel it is easier to write, to read, and to debug.

    The following code is not using minification safe dependencies.

    // *** avoid
    angular
        .module('app')
        .controller('Avengers', Avengers);
    
    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);
        }
    }

    Instead, include the dependencies in the module definition, and ng-annotate will create the proper injections.

    // *** use
    angular
        .module('app', 
        ['storageService', 'avengerService'])
        .controller('Avengers', Avengers);
    
    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);
        }
    }
    // ng-annotate will inject:
    //    Avengers.$inject = ['storageService', 'avengerService'];
  • Wrap all modules (using gulp-warp or grunt-wrap) with:

	(function(){
	 'use strict';
	 // <%= contents %> go here
	 })();

Note: Only wrap your own code, as messing with libraries can have un-expected results.

Back to top

Exception Handling

decorators (100%)

[Style Y110]
  • Use a decorator, at config time using the $provide service, on the $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.

    Note: Another option is to override the service instead of using a decorator. This is a fine option, but if you want to keep the default behavior and extend it a decorator is recommended.

    /* 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 (100%)

[Style Y111]
  • 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).

    Note: The exception catcher is good for catching and reacting to specific exceptions from calls that you know may throw one. For example, when making an XHR call to retrieve data from a remote web service and you want to catch any exceptions from that service and react uniquely.

    /* recommended */
    angular
        .module('blocks.exception', ['$logger'])
        .factory('exception', exception);
    
    function exception(logger) {
        var service = {
            catcher: catcher
        };
        return service;
    
        function catcher(message) {
            return function(reason) {
                logger.error(message, reason);
            };
        }
    }

Route Errors

[Style Y112]
  • Handle and log all routing errors using $stateChangeError .

    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.

    /* 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('$stateChangeError',
            function(event, toState, toParams, fromState, fromParams, error) {
                var destination = (toState&& (toState.title || toState.name || toState. url)) ||
                    'unknown target';
                var msg = 'Error routing to ' + destination + '. ' + (error.msg || '');
                /**
                 * Optionally log using a custom service or $log.
                  */
                logger.warning(msg, [current]);
            }
        );
    }

Back to top

Naming

Naming Guidelines (90% - added app)

[Style Y120]
  • Use consistent names for all components following a pattern that describes the component's feature then (optionally) its type. Our recommended pattern is feature.type.js. There are 2 names for most assets:

    • the file name (avengers.controller.js)
    • the registered component name with Angular (AvengersController)

    Why?: Naming conventions help provide a consistent way to find content at a glance. Consistency within the project is vital. Consistency with a team is important. Consistency across a company provides tremendous efficiency.

    Why?: The naming conventions should simply help you find your code faster and make it easier to understand.

We prepend the module names with the application mnemonic. If our app was 'game', our namespace pattern would be game.feature.type.js

Feature File Names (simplified)

[Style Y121]
  • Use consistent names for all components following a pattern that describes the component's feature then (optionally) its type. My recommended pattern is app.feature.type.js.

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

    Why?: Provides pattern matching for any automated tasks.

    /**
    * recommended
    */
    
    // controllers
    avengers.js
    avengersController.js
    
    // services/factories
    logger.service.js
    logger.service.spec.js
    
    // constants
    constants.js
    
    // module definition
    avengers.module.js
    
    // routes
    avengers.routes.js
    avengers.routes.spec.js
    
    // configuration
    avengers.config.js
    
    // directives
    avenger-profile.directive.js
    avenger-profile.directive.spec.js

Note: We prefer the common convention of 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.

Test File Names (100%)

[Style Y122]
  • Name test specifications similar to the component they test with a suffix of spec. Place them in the same folder as the target.

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

    Why?: Provides pattern matching for karma or other test runners.

    /**
     * recommended
     */
    avengers.controller.spec.js
    logger.service.spec.js
    avengers.routes.spec.js
    avenger-profile.directive.spec.js

Controller Names (100%)

[Style Y123]
  • Use consistent names for all controllers named after their feature. Use UpperCamelCase for controllers, as they are constructors.

    Why?: Provides a consistent way to quickly identify and reference controllers.

    Why?: UpperCamelCase is conventional for identifying object that can be instantiated using a constructor.

    /**
     * recommended
     */
    
    // avengers.controller.js
    angular
        .module
        .controller('HeroAvengers', HeroAvengers);
    
    function HeroAvengers(){ }

Service Names (100%)

[Style Y125]
  • Use consistent names for all factories named after their feature. Use camel-casing for services and factories.

    Why?: Provides a consistent way to quickly identify and reference factories.

    /**
     * recommended
     */
    
    // logger.service.js
    angular
        .module
        .factory('logger', logger);
    
    function logger(){ }

Model Names

[Style C125]
  • Use UpperCased model names for Model factories, as they are constructors.

    angular.module('game.models.robot', [])
    	.factory('Robot', Robot);

See Models.

Directive Component Names (100%)

[Style Y126]
  • Use consistent names for all directives using camel-case. Use a short prefix to describe the area that the directives belong (some example are company prefix or project prefix).

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

    /**
     * recommended
     */
    
    // avenger-profile.directive.js    
    angular
        .module
        .directive('xxAvengerProfile', xxAvengerProfile);
    
    // usage is <xx-avenger-profile> </xx-avenger-profile>
    
    function xxAvengerProfile(){ }

Modules (100%)

[Style Y127]
  • When there are multiple modules, the main module file is named app.module.js while other dependent modules are named after what they represent. For example, an admin module is named admin.module.js. The respective registered module names would be app and admin.
*Why?*: Provides consistency for multiple module apps, and for expanding to large applications.

*Why?*: Provides easy way to use task automation to load all module definitions first, then all other angular files (for bundling).

Configuration (100%)

[Style Y128]
  • Separate configuration for a module into its own file named after the module. A configuration file for the main app module is named app.config.js (or simply config.js). A configuration for a module named admin.module.js is named admin.config.js.

    Why?: Separates configuration from module definition, components, and active code.

    Why?: Provides a identifiable place to set configuration for a module.

Routes (0%)

[Style C129]
  • While Papa prefers routing in its own module, we find having the config in each controller makes more sense.

Back to top

Application Structure LIFT Principle (100%)

LIFT

[Style Y140]
  • Structure your app such that you can Locate your code quickly, Identify the code at a glance, keep the Flattest structure you can, and Try to stay DRY. The structure should follow these 4 basic guidelines.

    Why LIFT?: Provides a consistent structure that scales well, is modular, and makes it easier to increase developer efficiency by finding code quickly. Another way to check your app structure is to ask yourself: How quickly can you open and work in all of the related files for a feature?

    When I find my structure is not feeling comfortable, I go back and revisit these LIFT guidelines

    1. Locating our code is easy
    2. Identify code at a glance
    3. Flat structure as long as we can
    4. Try to stay DRY (Don’t Repeat Yourself) or T-DRY

Locate

[Style Y141]
  • Make locating your code intuitive, simple and fast.

    Why?: I find this to be super important for a project. If the team cannot find the files they need to work on quickly, they will not be able to work as efficiently as possible, and the structure needs to change. You may not know the file name or where its related files are, so putting them in the most intuitive locations and near each other saves a ton of time. A descriptive folder structure can help with this.

    /bower_components
    /client
      /app
        /avengers
        /blocks
          /exception
          /logger
        /core
        /dashboard
        /data
        /models
        /layout
        /widgets
      /content
      index.html
    .bower.json
    

Identify

[Style Y142]
  • When you look at a file you should instantly know what it contains and represents.

    Why?: You spend less time hunting and pecking for code, and become more efficient. If this means you want longer file names, then so be it. Be descriptive with file names and keeping the contents of the file to exactly 1 component. Avoid files with multiple controllers, multiple services, or a mixture. There are deviations of the 1 per file rule when I have a set of very small features that are all related to each other, they are still easily identifiable.

Flat

[Style Y143]
  • Keep a flat folder structure as long as possible. When you get to 7+ files, begin considering separation.

    Why?: Nobody wants to search 7 levels of folders to find a file. Think about menus on web sites … anything deeper than 2 should take serious consideration. In a folder structure there is no hard and fast number rule, but when a folder has 7-10 files, that may be time to create subfolders. Base it on your comfort level. Use a flatter structure until there is an obvious value (to help the rest of LIFT) in creating a new folder.

T-DRY (Try to Stick to DRY)

[Style Y144]
  • Be DRY, but don't go nuts and sacrifice readability.

    Why?: Being DRY is important, but not crucial if it sacrifices the others in LIFT, which is why I call it T-DRY. I don’t want to type session-view.html for a view because, well, it’s obviously a view. If it is not obvious or by convention, then I name it.

Back to top

Application Structure (100%)

Overall Guidelines

[Style Y150]
  • Have a near term view of implementation and a long term vision. In other words, start small and but keep in mind on where the app is heading down the road. All of the app's code goes in a root folder named app. All content is 1 feature per file. Each controller, service, module, view is in its own file. All 3rd party vendor scripts are stored in another root folder and not in the app folder. I didn't write them and I don't want them cluttering my app (bower_components, scripts, lib).
Note: Find more details and reasoning behind the structure at [this original post on application structure](http://www.johnpapa.net/angular-app-structuring-guidelines/).

Layout

[Style Y151]
  • Place components that define the overall layout of the application in a folder named layout. These may include a shell view and controller may act as the container for the app, navigation, menus, content areas, and other regions.

    Why?: Organizes all layout in a single place re-used throughout the application.

Folders-by-Feature Structure

[Style Y152]
  • Create folders named for the feature they represent. When a folder grows to contain more than 7 files, start to consider creating a folder for them. Your threshold may be different, so adjust as needed.

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

    Why?: The LIFT guidelines are all covered.

    Why?: Helps reduce the app from becoming cluttered through organizing the content and keeping them aligned with the LIFT guidelines.

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

    /**
     * recommended
     */
    
    app/
        app.module.js
        app.config.js
        app.routes.js
        components/       
            calendar.directive.js  
            calendar.directive.html  
            user-profile.directive.js  
            user-profile.directive.html  
        layout/
            shell.html      
            shell.controller.js
            topnav.html      
            topnav.controller.js       
        people/
            attendees.html
            attendees.controller.js  
            speakers.html
            speakers.controller.js
            speaker-detail.html
            speaker-detail.controller.js
        services/       
            data.service.js  
            localstorage.service.js
            logger.service.js   
            spinner.service.js
        sessions/
            sessions.html      
            sessions.controller.js
            session-detail.html
            session-detail.controller.js  

    Sample App Structure

    Note: Do not use structuring using folders-by-type. This requires moving to multiple folders when working on a feature and gets unwieldy quickly as the app grows to 5, 10 or 25+ views and controllers (and other features), which makes it more difficult than folder-by-feature to locate files.

    /* 
    * avoid
    * Alternative folders-by-type.
    * I recommend "folders-by-feature", instead.
    */
    
    app/
        app.module.js
        app.config.js
        app.routes.js
        controllers/
            attendees.js            
            session-detail.js       
            sessions.js             
            shell.js                
            speakers.js             
            speaker-detail.js       
            topnav.js               
        directives/       
            calendar.directive.js  
            calendar.directive.html  
            user-profile.directive.js  
            user-profile.directive.html  
        services/       
            dataservice.js  
            localstorage.js
            logger.js   
            spinner.js
        views/
            attendees.html     
            session-detail.html
            sessions.html      
            shell.html         
            speakers.html      
            speaker-detail.html
            topnav.html         

Back to top

Modularity (100%)

Many Small, Self Contained Modules

[Style Y160]
  • Create small modules that encapsulate 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.

Create an App Module

[Style Y161]
  • Create an application root module whose role is pull together all of the modules and features of your application. Name this for your application.

    Why?: AngularJS encourages modularity and separation patterns. Creating an application root module whose role is to tie your other modules together provides a very straightforward way to add or remove modules from your application.

Keep the App Module Thin

[Style Y162]
  • Only put logic for pulling together the app in the application module. Leave features in their own modules.

    Why?: Adding additional roles to the application root to get remote data, display views, or other logic not related to pulling the app together muddies the app module and make both sets of features harder to reuse or turn off.

    Why?: The app module becomes a manifest that describes which modules help define the application.

We often make the app controller an abstract state (using ui-router). This allows us to ensure data dependencies on an app-wide basis are loaded.

Feature Areas are Modules

[Style Y163]
  • 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 with 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

[Style Y164]
  • 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 Dependencies

[Style Y165]
  • The application root module depends on the app specific feature modules and any shared or reusable modules.

    Modularity and Dependencies

    Why?: The main app module contains a quickly identifiable manifest of the application's features.

    Why?: Each feature area contains a manifest of what it depends on, so it can be pulled in as a dependency in other applications and still work.

    Why?: Intra-App features such as shared data services become easy to locate and share from within app.core (choose your favorite name for this module).

    Note: This is a strategy for consistency. There are many good options here. Choose one that is consistent, follows AngularJS's dependency rules, and is easy to maintain and scale.

    My structures vary slightly between projects but they all follow these guidelines for structure and modularity. The implementation may vary depending on the features and the team. In other words, don't get hung up on an exact like-for-like structure but do justify your structure using consistency, maintainability, and efficiency in mind.

    In a small app, you can also consider putting all the shared dependencies in the app module where the feature modules have no direct dependencies. This makes it easier to maintain the smaller application, but makes it harder to reuse modules outside of this application.

Back to top

Startup Logic (100%)

Configuration

[Style Y170]
  • Inject code into module configuration that must be configured before running the angular app. Ideal candidates include providers and constants.

    Why?: This makes it easier to have a less places for configuration.

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]
  • Any code that needs to run when an application starts should be declared in a factory, exposed via a function, and injected into the run block.

    Why?: Code directly in a run block can be difficult to test. Placing in a factory makes it easier to abstract and mock.

angular
    .module('app')
    .run(runBlock);

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

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

Back to top

Angular $ Wrapper Services (100%)

$document and $window

[Style Y180]
  • 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

[Style Y181]
  • 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

Testing (100%)

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]
  • Write a set of tests for every story. Start with an empty test and fill them in as you write the code for the story.

    Why?: Writing the test descriptions helps clearly define what your story will do, will not do, and how you can measure success.

    it('should have Avengers controller', function() {
        //TODO
    });
    
    it('should find 1 Avenger when filtered by name', function() {
        //TODO
    });
    
    it('should have 10 Avengers', function() {
        //TODO (mock data?)
    });
    
    it('should return Avengers via XHR', function() {
        //TODO ($httpBackend?)
    });
    
    // and so on

Testing Library (narrowed)

[Style Y191]
  • Use Jasmine for unit testing.

    Why?: Jasmine is widely used in the AngularJS community. It is stable, well maintained, and provides robust testing features.

Test Runner

[Style Y192]
  • Use Karma as a test runner.

    Why?: Karma is easy to configure to run once or automatically when you change your code.

    Why?: Karma hooks into your Continuous Integration process easily on its own or through Grunt or Gulp.

    Why?: Some IDE's are beginning to integrate with Karma, such as WebStorm and Visual Studio.

    Why?: Karma works well with task automation leaders such as Grunt (with grunt-karma) and Gulp (with gulp-karma).

Headless Browser

[Style Y194]
  • Use PhantomJS to run your tests on a server.

    Why?: PhantomJS is a headless browser that helps run your tests without needing a "visual" browser. So you do not have to install Chrome, Safari, IE, or other browsers on your server.

    Note: You should still test on all browsers in your environment, as appropriate for your target audience.

Code Coverage

[Style C195]
  • Use Karma Coverage to analyze test coverage.

    Why? You'll find most of your errors where the code is not tested.

  • Write additional tests when killing bugs. Whenever you are debugging and find a bug, if possible, write an additional test that covers the bug.

Protractor Tests

[Style C200]
  • Where possible, write basic protractor tests to verify the UI functionality.

Why? Automated tests ensure higher quality, Unit tests can't catch functional interactions.

Code Analysis

[Style Y195]
  • Run JSHint on your tests.

    Why?: Tests are code. JSHint can help identify code quality issues that may cause the test to work improperly.

Alleviate Globals for JSHint Rules on Tests

[Style Y196]
  • Relax the rules on your test code to allow for common globals such as describe and expect.

    Why?: Your tests are code and require the same attention and code quality rules as all of your production code. However, global variables used by the testing framework, for example, can be relaxed by including this in your test specs.

    /* global sinon, describe, it, afterEach, beforeEach, expect, inject */

Testing Tools

Back to top

Animations (100%)

Usage

[Style Y210]
  • Use subtle animations with AngularJS to transition between states for views and primary visual elements. Include the ngAnimate module. The 3 keys are subtle, smooth, seamless.

    Why?: Subtle animations can improve User Experience when used appropriately.

    Why?: Subtle animations can improve perceived performance as views transition.

Sub Second

[Style Y211]
  • Use short durations for animations. I generally start with 300ms and adjust until appropriate.

    Why?: Long animations can have the reverse affect on User Experience and perceived performance by giving the appearance of a slow application.

animate.css

[Style Y212]

Back to top

Comments (100%)

jsDoc

[Style Y220]
  • Use jsDoc syntax to document function names, description, params and returns. Use @namespace and @memberOf to match your app structure. Use @class, @property and @method for factory models. Use @param for all public function args (your can leave out injections). Always include the type of the param for all public functions.

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

    Why?: Provides consistency using a common industry tool.

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

Back to top

JS Hint (100%)

Use an Options File

[Style Y230]
  • Use JS Hint for linting your JavaScript and be sure to customize the JS Hint options file and include in source control. See the JS Hint docs for details on the options.

    Why?: Provides a first alert prior to committing any code to source control.

    Why?: Provides consistency across your team.

    {
        "bitwise": true,
        "camelcase": true,
        "curly": true,
        "eqeqeq": true,
        "es3": false,
        "forin": true,
        "freeze": true,
        "immed": true,
        "indent": 4,
        "latedef": "nofunc",
        "newcap": true,
        "noarg": true,
        "noempty": true,
        "nonbsp": true,
        "nonew": true,
        "plusplus": false,
        "quotmark": "single",
        "undef": true,
        "unused": false,
        "strict": false,
        "maxparams": 10,
        "maxdepth": 5,
        "maxstatements": 40,
        "maxcomplexity": 8,
        "maxlen": 120,
    
        "asi": false,
        "boss": false,
        "debug": false,
        "eqnull": true,
        "esnext": false,
        "evil": false,
        "expr": false,
        "funcscope": false,
        "globalstrict": false,
        "iterator": false,
        "lastsemic": false,
        "laxbreak": false,
        "laxcomma": false,
        "loopfunc": true,
        "maxerr": false,
        "moz": false,
        "multistr": false,
        "notypeof": false,
        "proto": false,
        "scripturl": false,
        "shadow": false,
        "sub": true,
        "supernew": false,
        "validthis": false,
        "noyield": false,
    
        "browser": true,
        "node": true,
    
        "globals": {
            "angular": false,
            "$": false
        }
    }

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.

###JSCS ######[Style C250]

  • JSCS is integrated into IntelliJ and WebStorm to provide continuous style checking against the Google code syntax.
  • Include gulp-jscs or grunt-jscs in the build process to catch unnoticed stylistic issues.

Why? Ensures on-going adherence to these standards.

Sublime Text

[Style Y250]
  • AngularJS snippets that follow these styles and guidelines.

    • Download the Sublime Angular snippets
    • Place it in your Packages folder
    • Restart Sublime
    • In a JavaScript file type these commands followed by a TAB
    ngcontroller // creates an Angular controller
    ngdirective // creates an Angular directive
    ngfactory // creates an Angular factory
    ngmodule // creates an Angular module

Visual Studio

[Style Y251]
  • AngularJS file templates that follow these styles and guidelines can be found at SideWaffle

    • Download the SideWaffle Visual Studio extension (vsix file)
    • Run the vsix file
    • Restart Visual Studio

WebStorm

[Style Y252]
  • AngularJS snippets and file templates that follow these styles and guidelines. You can import them into your WebStorm settings:

    ng-c // creates an Angular controller
    ng-f // creates an Angular factory
    ng-m // creates an Angular module

Back to top

AngularJS docs

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

Contributing

Open an issue first to discuss potential changes/additions. If you have questions with the guide, feel free to leave them as issues in the repository. If you find a typo, create a pull request. The idea is to keep the content up to date and use github’s native feature to help tell the story with issues and PR’s, which are all searchable via google. Why? Because odds are if you have a question, someone else does too! You can learn more here at about how to contribute.

By contributing to this repository you are agreeing to make your content available subject to the license of this repository.

Process

1. Discuss the changes in an Issue. 
2. Open a Pull Request, reference the issue, and explain the change and why it adds value.
3. The Pull Request will be evaluated and either merged or declined.

License

tldr; Use this guide. Attributions are appreciated.

(The MIT License)

Copyright (c) 2014 John Papa portions Copyright (c) 2015 AgileThought

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.

Back to top

About

V1.1: AngularJS Style Guide: A takeoff on John Papa's excellent guide. This is slanted for more of a opinionated MVC discipline. Thanks to rich input from many AgileThought brainiacs!

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published