diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/README.md b/README.md index b5b6fee..b25af13 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,15 @@ -valor-style-guides -================= +## Company-shared style guides and practices -# Company-shared style guides and practices - -##Must be read and applied: -- [node.js](nodejs/node-style-guide.md) -- angular.js style guides +### Must be read and applied: +- [node.js style guide](nodejs/node-style-guide.md) +- [angular.js style guide](angularjs/README.md) - [commit message guideline](contribution/contribution-guidelines.md) -- use [`eslint`](https://github.com/eslint/eslint) and knew `eslint` [rules](http://eslint.org/docs/rules) +- Code linting: [`eslint`](https://github.com/eslint/eslint) ### Apply linter rules to your projects: -* [.eslintrc](.eslintrc) - docs for [eslint](http://eslint.org/docs/rules/) +* ESLint options file: [.eslintrc](.eslintrc) ```js var eslint = require('gulp-eslint'); @@ -31,5 +28,5 @@ gulp.task('lint', function () { ### Force testing linting: * [pre-commit](https://www.npmjs.com/package/pre-commit) -Credits: +### Credits: * [eslint docs](http://eslint.org/docs/developer-guide/contributing.html) diff --git a/angularjs/README.md b/angularjs/README.md index 85f5846..0c9f91b 100644 --- a/angularjs/README.md +++ b/angularjs/README.md @@ -1,25 +1,21 @@ # Angular Style Guide -*Opinionated Angular style guide for teams by [@john_papa](//twitter.com/john_papa)* +*Opinionated Angular style guide for teams by [@valorkin](//twitter.com/valorkin)* -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](//angularjs.org), presentations, [Pluralsight training courses](http://pluralsight.com/training/Authors/Details/john-papa) and working in teams. +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 and Valor Software developers development experience with [Angular](//angularjs.org), presentations, 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](http://jpapa.me/ngclean) course at Pluralsight which is a companion to this guide. - - [![AngularJs Patterns: Clean Code](https://raw.githubusercontent.com/johnpapa/angular-styleguide/master/assets/ng-clean-code-banner.png)](http://jpapa.me/ngclean) - ## 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, a friend and 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](https://github.com/toddmotto/angularjs-styleguide) 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](http://twitter.com/wardbell) and I have had. My friend Ward has certainly helped influence the ultimate evolution of this guide. +Fill free to check guidelines used as a basis for this guideline: +- [Todd's guidelines](https://github.com/toddmotto/angularjs-styleguide) +- [John Papa's guidelines](https://github.com/johnpapa/angular-styleguide) ## See the Styles in a Sample App +Cooming soon + ## Table of Contents @@ -31,7 +27,7 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see 1. [Factories](#factories) 1. [Data Services](#data-services) 1. [Directives](#directives) - 1. [Resolving Promises for a Controller](#resolving-promises-for-a-controller) + 1. [Resolving a Controller](#resolving-a-controller) 1. [Manual Annotating for Dependency Injection](#manual-annotating-for-dependency-injection) 1. [Minification and Annotation](#minification-and-annotation) 1. [Exception Handling](#exception-handling) @@ -44,11 +40,8 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see 1. [Testing](#testing) 1. [Animations](#animations) 1. [Comments](#comments) - 1. [JSHint](#js-hint) - 1. [JSCS](#jscs) + 1. [ESLint](#eslint) 1. [Constants](#constants) - 1. [File Templates and Snippets](#file-templates-and-snippets) - 1. [Yeoman Generator](#yeoman-generator) 1. [Routing](#routing) 1. [Task Automation](#task-automation) 1. [Filters](#filters) @@ -59,7 +52,7 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ## Single Responsibility ### Rule of 1 -###### [Style [Y001](#style-y001)] + - Define 1 component per file. @@ -68,9 +61,9 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ```javascript /* avoid */ angular - .module('app', ['ngRoute']) - .controller('SomeController', SomeController) - .factory('someFactory', someFactory); + .module('app', ['ngRoute']) + .controller('SomeController', SomeController) + .factory('someFactory', someFactory); function SomeController() { } @@ -84,7 +77,7 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see // app.module.js angular - .module('app', ['ngRoute']); + .module('app', ['ngRoute']); ``` ```javascript @@ -92,10 +85,10 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see // someController.js angular - .module('app') - .controller('SomeController', SomeController); - - function SomeController() { } + .module('app') + .controller('SomeController', [ + function SomeController() {} + ]); ``` ```javascript @@ -103,90 +96,72 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see // someFactory.js angular - .module('app') - .factory('someFactory', someFactory); - - function someFactory() { } + .module('app') + .factory('someFactory', [ + function someFactory() {} + ]); ``` **[Back to top](#table-of-contents)** ## IIFE -### JavaScript Closures -###### [Style [Y010](#style-y010)] - - Wrap Angular components in an Immediately Invoked Function Expression (IIFE). +#### Good idea: + + - To avoid polluting the global scope with our function declarations + that get passed into Angular, ensure build tasks wrap the concatenated + files inside an 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. +#### But: - *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. + - I see wrapping each component in IIFE hard and not necessary. + Just keep all required code inside of function passed to Angular. ```javascript /* avoid */ - // logger.js - angular - .module('app') - .factory('logger', logger); - - // logger function is added as a global variable - function logger() { } - // storage.js + // someFactory.js angular - .module('app') - .factory('storage', storage); + .module('app') + .factory('someFactory', [ + function someFactory() { + this.onClick = sayHello; + } + ]); - // storage function is added as a global variable - function storage() { } + function sayHello() {} ``` ```javascript - /** - * recommended - * - * no globals are left behind - */ - - // logger.js - (function() { - 'use strict'; - - angular - .module('app') - .factory('logger', logger); - - function logger() { } - })(); - - // storage.js - (function() { - 'use strict'; + /* recommended */ - angular - .module('app') - .factory('storage', storage); + // someFactory.js + angular + .module('app') + .factory('someFactory', [ + function someFactory() { + this.onClick = sayHello; - function storage() { } - })(); + function sayHello() {} + } + ]); ``` - - Note: For brevity only, the rest of the examples in this guide may omit the IIFE syntax. - - - 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. +Original: [Style [Y010](https://github.com/johnpapa/angular-styleguide#style-y010)] **[Back to top](#table-of-contents)** ## Modules ### Avoid Naming Collisions -###### [Style [Y020](#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`. ### Definitions (aka Setters) -###### [Style [Y021](#style-y021)] + - Declare modules without a variable using the setter syntax. @@ -195,10 +170,10 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ```javascript /* avoid */ var app = angular.module('app', [ - 'ngAnimate', - 'ngRoute', - 'app.shared', - 'app.dashboard' + 'ngAnimate', + 'ngRoute', + 'app.shared', + 'app.dashboard' ]); ``` @@ -207,16 +182,16 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ```javascript /* recommended */ angular - .module('app', [ - 'ngAnimate', - 'ngRoute', - 'app.shared', - 'app.dashboard' - ]); + .module('app', [ + 'ngAnimate', + 'ngRoute', + 'app.shared', + 'app.dashboard' + ]); ``` ### Getters -###### [Style [Y022](#style-y022)] + - When using a module, avoid using a variable and instead use chaining with the getter syntax. @@ -225,22 +200,22 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ```javascript /* avoid */ var app = angular.module('app'); - app.controller('SomeController', SomeController); - - function SomeController() { } + app.controller('SomeController', [ + function SomeController() { } + ]); ``` ```javascript /* recommended */ angular - .module('app') - .controller('SomeController', SomeController); - - function SomeController() { } + .module('app') + .controller('SomeController', [ + function SomeController() { } + ]); ``` ### Setting vs Getting -###### [Style [Y023](#style-y023)] + - Only set once and get for all other instances. @@ -250,7 +225,7 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see - Use `angular.module('app');` to get a module. ### Named vs Anonymous Functions -###### [Style [Y024](#style-y024)] + - Use named functions instead of passing an anonymous function in as a callback. @@ -259,9 +234,9 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ```javascript /* avoid */ angular - .module('app') - .controller('Dashboard', function() { }) - .factory('logger', function() { }); + .module('app') + .controller('Dashboard', function() { }) + .factory('logger', function() { }); ``` ```javascript @@ -269,19 +244,19 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see // dashboard.js angular - .module('app') - .controller('Dashboard', Dashboard); - - function Dashboard() { } + .module('app') + .controller('Dashboard', [ + function Dashboard() { } + ]); ``` ```javascript // logger.js angular - .module('app') - .factory('logger', logger); - - function logger() { } + .module('app') + .factory('logger', [ + function logger() { } + ]); ``` **[Back to top](#table-of-contents)** @@ -289,7 +264,9 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ## Controllers ### controllerAs View Syntax -###### [Style [Y030](#style-y030)] + + +#### *Important: never use `ngController` directive* - Use the [`controllerAs`](http://www.johnpapa.net/do-you-like-your-angular-controllers-with-or-without-sugar/) syntax over the `classic controller with $scope` syntax. @@ -302,19 +279,19 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ```html
- {{ name }} + {{ name }}
``` ```html - +
- {{ customer.name }} + {{ customer.name }}
``` ### controllerAs Controller Syntax -###### [Style [Y031](#style-y031)] + - Use the `controllerAs` syntax over the `classic controller with $scope` syntax. @@ -327,72 +304,74 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ```javascript /* avoid */ function Customer($scope) { - $scope.name = {}; - $scope.sendMessage = function() { }; + $scope.name = {}; + $scope.sendMessage = function() { }; } ``` ```javascript /* recommended - but see next section */ function Customer() { - this.name = {}; - this.sendMessage = function() { }; + this.name = {}; + this.sendMessage = function() { }; } ``` -### controllerAs with vm -###### [Style [Y032](#style-y032)] +### controllerAs with `self` + - - Use a capture variable for `this` when using the `controllerAs` syntax. Choose a consistent variable name such as `vm`, which stands for ViewModel. + - Use a capture variable for `this` when using the `controllerAs` syntax. + Choose a variable name such as `self` to + [keep `this` usage consistent](http://eslint.org/docs/rules/consistent-this). + + *or: 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. ```javascript /* avoid */ function Customer() { - this.name = {}; - this.sendMessage = function() { }; + this.name = {}; + this.sendMessage = function() { }; } ``` ```javascript /* recommended */ function Customer() { - var vm = this; - vm.name = {}; - vm.sendMessage = function() { }; + var self = this; + self.name = {}; + self.sendMessage = function() { }; } ``` - Note: You can avoid any [jshint](http://www.jshint.com/) 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. - - ```javascript - /* 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.) + Note: When creating watches in a controller using `controller as`, you can watch the `ctrl.*` member using the following syntax. (Create watches with caution as they add more load to the digest cycle.) ```html - + ``` ```javascript function SomeController($scope, $log) { - var vm = this; - vm.title = 'Some Title'; + var self = this; + self.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); - }); + $scope.$watch('someCtrl.title', function(current, original) { + $log.info('someCtrl.title was %s', original); + $log.info('someCtrl.title is now %s', current); + }); } ``` ### Bindable Members Up Top -###### [Style [Y033](#style-y033)] + - - Place bindable members at the top of the controller, alphabetized, and not spread through the controller code. + - Place bindable members at the top of the controller, and not spread through the controller code. + - Proposed order: + - bind default values + - bind event handlers + - initiate controller + - event handlers declarations *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. @@ -401,85 +380,94 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ```javascript /* avoid */ function Sessions() { - var vm = this; + var self = this; - vm.gotoSession = function() { - /* ... */ - }; - vm.refresh = function() { - /* ... */ - }; - vm.search = function() { - /* ... */ - }; - vm.sessions = []; - vm.title = 'Sessions'; + self.gotoSession = function() { + /* ... */ + }; + self.refresh = function() { + /* ... */ + }; + self.search = function() { + /* ... */ + }; + self.sessions = []; + self.title = 'Sessions'; + } ``` ```javascript /* recommended */ - function Sessions() { - var vm = this; + function SessionsController() { + var self = this; - vm.gotoSession = gotoSession; - vm.refresh = refresh; - vm.search = search; - vm.sessions = []; - vm.title = 'Sessions'; + // bind default values + self.sessions = []; + self.title = 'Sessions'; - //////////// + // bind event handlers + self.gotoSession = gotoSession; + self.refresh = refresh; + self.search = search; - function gotoSession() { - /* */ - } + // initiate controller + activate(); - function refresh() { - /* */ - } + // event handler declarations + function gotoSession() { + /* */ + } - function search() { - /* */ - } - ``` + function refresh() { + /* */ + } - ![Controller Using "Above the Fold"](https://raw.githubusercontent.com/johnpapa/angular-styleguide/master/assets/above-the-fold-1.png) + function search() { + /* */ + } + } + ``` Note: If the function is a 1 liner consider keeping it right up top, as long as readability is not affected. ```javascript /* avoid */ - function Sessions(data) { - var vm = this; + function SessionsController(data) { + var self = this; - vm.gotoSession = gotoSession; - vm.refresh = function() { - /** - * lines - * of - * code - * affects - * readability - */ - }; - vm.search = search; - vm.sessions = []; - vm.title = 'Sessions'; + self.sessions = []; + self.title = 'Sessions'; + + self.gotoSession = gotoSession; + self.refresh = function() { + /** + * lines + * of + * code + * affects + * readability + */ + }; + self.search = search; + } ``` ```javascript /* recommended */ - function Sessions(dataservice) { - var vm = this; - - vm.gotoSession = gotoSession; - vm.refresh = dataservice.refresh; // 1 liner is OK - vm.search = search; - vm.sessions = []; - vm.title = 'Sessions'; + function SessionsController(dataservice) { + var self = this; + + self.sessions = []; + self.title = 'Sessions'; + + self.gotoSession = gotoSession; + self.refresh = dataservice.refresh; // 1 liner is OK + self.search = search; + } ``` ### Function Declarations to Hide Implementation Details -###### [Style [Y034](#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](http://www.johnpapa.net/angular-function-declarations-function-expressions-and-readable-code). @@ -499,30 +487,30 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see * Using function expressions. */ function Avengers(dataservice, logger) { - var vm = this; - vm.avengers = []; - vm.title = 'Avengers'; + var self = this; + self.avengers = []; + self.title = 'Avengers'; - var activate = function() { - return getAvengers().then(function() { - logger.info('Activated Avengers View'); - }); - } + 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; - }); - } + var getAvengers = function() { + return dataservice.getAvengers().then(function(data) { + self.avengers = data; + return self.avengers; + }); + } - vm.getAvengers = getAvengers; + self.getAvengers = getAvengers; - activate(); + 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. + 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 `self.avengers` and `self.title`. The implementation details are down below. This is just easier to read. ```javascript /* @@ -531,30 +519,31 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see * and bindable members up top. */ function Avengers(dataservice, logger) { - var vm = this; - vm.avengers = []; - vm.getAvengers = getAvengers; - vm.title = 'Avengers'; + var self = this; + self.avengers = []; + self.title = 'Avengers'; - activate(); + self.getAvengers = getAvengers; - function activate() { - return getAvengers().then(function() { - logger.info('Activated Avengers View'); - }); - } + activate(); - function getAvengers() { - return dataservice.getAvengers().then(function(data) { - vm.avengers = data; - return vm.avengers; - }); - } + function activate() { + return getAvengers().then(function() { + logger.info('Activated Avengers View'); + }); + } + + function getAvengers() { + return dataservice.getAvengers().then(function(data) { + self.avengers = data; + return self.avengers; + }); + } } ``` ### Defer Controller Logic to Services -###### [Style [Y035](#style-y035)] + - Defer logic in a controller by delegating to services and factories. @@ -570,84 +559,84 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see /* 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 - }); - }; + var self = this; + self.checkCredit = checkCredit; + self.isCreditOk; + self.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 + self.isCreditOk = self.total <= maxRemainingAmount + }) + .catch(function(error) { + // Interpret error + // Cope w/ timeout? retry? try alternate service? + // Re-reject with appropriate error for a user to see + }); + }; } ``` ```javascript /* 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); - }; + var self = this; + self.isCreditOk; + self.total = 0; + + self.checkCredit = checkCredit; + + function checkCredit() { + return creditService.isOrderTotalOk(self.total) + .then(function(isOk) { self.isCreditOk = isOk; }) + .catch(showServiceError); + }; } ``` ### Keep Controllers Focused -###### [Style [Y037](#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](#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`](https://docs.angularjs.org/api/ng/directive/ngController), that view is always associated with the same controller. + *Why?*: Pairing the controller in the route allows different routes to invoke different pairs of controllers and views. ```javascript /* avoid - when using with a route and dynamic pairing is desired */ // route-config.js angular - .module('app') - .config(config); - - function config($routeProvider) { + .module('app') + .config([ + '$routeProvider', + function config($routeProvider) { $routeProvider - .when('/avengers', { - templateUrl: 'avengers.html' - }); - } + .when('/avengers', { + templateUrl: 'avengers.html' + }); + } + ]); ``` ```html - -
+ +
``` @@ -656,23 +645,23 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see // route-config.js angular - .module('app') - .config(config); - - function config($routeProvider) { - $routeProvider + .module('app') + .config([ + '$routeProvider', + function config($routeProvider) { + $routeProvider .when('/avengers', { - templateUrl: 'avengers.html', - controller: 'Avengers', - controllerAs: 'vm' + templateUrl: 'avengers.html', + controller: 'AvengersController', + controllerAs: 'avengersCtrl' }); - } + } + ]); ``` ```html -
-
+
``` **[Back to top](#table-of-contents)** @@ -680,38 +669,42 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ## Services ### Singletons -###### [Style [Y040](#style-y040)] + - Services are instantiated with the `new` keyword, use `this` for public methods and variables. Since these are so similar to factories, use a factory instead for consistency. Note: [All Angular services are singletons](https://docs.angularjs.org/guide/services). This means that there is only one instance of a given service per injector. + Note: [Ng Source service vs factory](https://github.com/angular/angular.js/blob/master/src/auto/injector.js#L686-L696) + ```javascript // service angular - .module('app') - .service('logger', logger); - - function logger() { - this.logError = function(msg) { - /* */ - }; - } + .module('app') + .service('logger', [ + function logger() { + this.logError = function(msg) { + /* */ + }; + } + ]); ``` ```javascript // factory + /* avoid - returning raw object, see later */ angular - .module('app') - .factory('logger', logger); - - function logger() { - return { + .module('app') + .factory('logger', [ + function logger() { + return { logError: function(msg) { /* */ } - }; - } + }; + } + ]); + ``` **[Back to top](#table-of-contents)** @@ -719,19 +712,19 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ## Factories ### Single Responsibility -###### [Style [Y050](#style-y050)] + - Factories should have a [single responsibility](http://en.wikipedia.org/wiki/Single_responsibility_principle), that is encapsulated by its context. Once a factory begins to exceed that singular purpose, a new factory should be created. ### Singletons -###### [Style [Y051](#style-y051)] + - Factories are singletons and return an object that contains the members of the service. Note: [All Angular services are singletons](https://docs.angularjs.org/guide/services). ### Accessible Members Up Top -###### [Style [Y052](#style-y052)] + - Expose the callable members of the service (its interface) at the top, using a technique derived from the [Revealing Module Pattern](http://addyosmani.com/resources/essentialjsdesignpatterns/book/#revealingmodulepatternjavascript). @@ -753,9 +746,9 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see }; return { - save: save, - someValue: someValue, - validate: validate + save: save, + someValue: someValue, + validate: validate }; } ``` @@ -763,32 +756,30 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ```javascript /* recommended */ function dataService() { - var someValue = ''; - var service = { - save: save, - someValue: someValue, - validate: validate - }; - return service; + var someValue = ''; + var service = { + save: save, + someValue: someValue, + validate: validate + }; + return service; - //////////// + //////////// - function save() { - /* */ - }; + function save() { + /* */ + }; - function validate() { - /* */ - }; + function validate() { + /* */ + }; } ``` This way bindings are mirrored across the host object, primitive values cannot update alone using the revealing module pattern. - ![Factories Using "Above the Fold"](https://raw.githubusercontent.com/johnpapa/angular-styleguide/master/assets/above-the-fold-2.png) - ### Function Declarations to Hide Implementation Details -###### [Style [Y053](#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](http://www.johnpapa.net/angular-function-declarations-function-expressions-and-readable-code). @@ -852,14 +843,16 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see var isPrimed = false; var primePromise; - var service = { + function DataService() {} + + DataService.prototype = { getAvengersCast: getAvengersCast, getAvengerCount: getAvengerCount, getAvengers: getAvengers, ready: ready }; - return service; + return new DataService(); //////////// @@ -890,7 +883,7 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ## Data Services ### Separate Data Calls -###### [Style [Y060](#style-y060)] + - Refactor logic for making data operations and interacting with data to a factory. Make data services responsible for XHR calls, local storage, stashing in memory, or any other data operations. @@ -906,29 +899,32 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see // dataservice factory angular .module('app.core') - .factory('dataservice', dataservice); - - dataservice.$inject = ['$http', 'logger']; + .factory('dataservice', [ + '$http', 'logger', + function dataservice($http, logger) { + function AvengeresRepository() {} + AvengeresRepository.prototype = { + getAvengers: getAvengers + }; - function dataservice($http, logger) { - return { - getAvengers: getAvengers - }; + return new AvengeresRepository() {} - function getAvengers() { - return $http.get('/api/maa') - .then(getAvengersComplete) - .catch(getAvengersFailed); + function getAvengers(cb) { + return $http.get('/api/maa') + .then(getAvengersComplete) + .catch(getAvengersFailed); - function getAvengersComplete(response) { - return response.data.results; - } + function getAvengersComplete(response) { + return cb(null, response.data.results); + } - function getAvengersFailed(error) { - logger.error('XHR Failed for getAvengers.' + error.data); + function getAvengersFailed(error) { + logger.error('XHR Failed for getAvengers.' + error.data); + return cb(error.data); + } } - } - } + } + ]); ``` Note: The data service is called from consumers, such as a controller, hiding the implementation from the consumers, as shown below. @@ -939,82 +935,46 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see // 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'); - }); - } + .controller('Avengers', [ + 'dataservice', 'logger', + function Avengers(dataservice, logger) { + var self = this; + self.avengers = []; + + activate(); + + function activate() { + return getAvengers(function() { + logger.info('Activated Avengers View'); + }); + } - function getAvengers() { - return dataservice.getAvengers() - .then(function(data) { - vm.avengers = data; - return vm.avengers; - }); - } - } + function getAvengers(cb) { + return dataservice + .getAvengers(function(err, data) { + self.avengers = data; + return cb(err, data); + }); + } + } + ]); ``` -### Return a Promise from Data Calls -###### [Style [Y061](#style-y061)] - - - When calling a data service that returns a promise such as `$http`, return a promise in your calling function too. - - *Why?*: You can chain the promises together and take further action after the data call completes and resolves or rejects the promise. +### **NEVER** Return a Promise from Data Calls + - ```javascript - /* recommended */ + - When calling a data service that returns a promise such as `$http`, never return a promise in your calling function. + - Always use callbacks with applying of `node.js` signature convention `function(err, data)` - activate(); + *Why?*: It will be really hard to debug promise chains - 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; - }); - } - ``` + - Use promises only to integrate with events outside of `angular` `$digest` **[Back to top](#table-of-contents)** ## Directives ### Limit 1 Per File -###### [Style [Y070](#style-y070)] + - Create one directive per file. Name the file for the directive. @@ -1037,9 +997,6 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see /* 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 */ } @@ -1047,10 +1004,6 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see function salesCustomerInfo() { /* implementation details */ } - - function sharedSpinner() { - /* implementation details */ - } ``` ```javascript @@ -1063,11 +1016,11 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see */ angular .module('sales.order') - .directive('acmeOrderCalendarRange', orderCalendarRange); - - function orderCalendarRange() { - /* implementation details */ - } + .directive('acmeOrderCalendarRange', [ + function orderCalendarRange() { + /* implementation details */ + } + ]); ``` ```javascript @@ -1080,41 +1033,24 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see */ angular .module('sales.widgets') - .directive('acmeSalesCustomerInfo', salesCustomerInfo); - - function salesCustomerInfo() { - /* implementation details */ - } - ``` - - ```javascript - /* recommended */ - /* spinner.directive.js */ - - /** - * @desc spinner directive that can be used anywhere across apps at a company named Acme - * @example
- */ - angular - .module('shared.widgets') - .directive('acmeSharedSpinner', sharedSpinner); - - function sharedSpinner() { - /* implementation details */ - } + .directive('acmeSalesCustomerInfo', [ + function salesCustomerInfo() { + /* 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](#naming) section for more recommendations. ### Manipulate DOM in a Directive -###### [Style [Y072](#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](https://docs.angularjs.org/api/ngAnimate), Angular templating, [`ngShow`](https://docs.angularjs.org/api/ng/directive/ngShow) or [`ngHide`](https://docs.angularjs.org/api/ng/directive/ngHide), then use those instead. For example, if the directive simply hides and shows, use ngHide/ngShow. *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 -###### [Style [Y073](#style-y073)] + - Provide a short, unique and descriptive directive prefix such as `acmeSalesCustomerInfo` which would be declared in HTML as `acme-sales-customer-info`. @@ -1123,7 +1059,7 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see Note: Avoid `ng-` as these are reserved for Angular directives. Research widely used directives to avoid naming conflicts, such as `ion-` for the [Ionic Framework](http://ionicframework.com/). ### Restrict to Elements and Attributes -###### [Style [Y074](#style-y074)] + - When creating a directive that makes sense as a stand-alone 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 it's stand-alone and as an attribute when it enhances its existing DOM element. @@ -1142,20 +1078,20 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see /* 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) { - /* */ - } - } + .directive('myCalendarRange', [ + function myCalendarRange() { + var directive = { + link: link, + templateUrl: '/template/is/located/here.html', + restrict: 'C' + }; + return directive; + + function link(scope, element, attrs) { + /* */ + } + } + ]); ``` ```html @@ -1168,24 +1104,24 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see /* 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) { - /* */ - } - } + .directive('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](#style-y075)] + - Use `controller as` syntax with a directive to be consistent with using `controller as` with view and controller pairings. @@ -1202,68 +1138,76 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ``` ```javascript + // my-example-directive.js 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', - 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']; + .directive('myExample', [ + function myExample() { + var directive = { + restrict: 'EA', + templateUrl: 'app/feature/example.directive.html', + scope: { + max: '=' + }, + link: linkFunc, + controller: ExampleController, + controllerAs: 'ctrl', + bindToController: true // because the scope is isolated + }; - function ExampleController($scope) { - // Injecting $scope just for comparison - var vm = this; + return directive; - vm.min = 3; + 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.self.min = %s', scope.self.min); + console.log('LINK: scope.self.max = %s', scope.self.max); + } + } + ]); +``` - console.log('CTRL: $scope.vm.min = %s', $scope.vm.min); - console.log('CTRL: $scope.vm.max = %s', $scope.vm.max); - console.log('CTRL: vm.min = %s', vm.min); - console.log('CTRL: vm.max = %s', vm.max); - } +```javascript + // example-controller.js + angular + .module('app') + .controller('ExampleController', [ + '$scope', + function ExampleController($scope) { + // Injecting $scope just for comparison + var self = this; + + self.min = 3; + + console.log('CTRL: $scope.self.min = %s', $scope.self.min); + console.log('CTRL: $scope.self.max = %s', $scope.self.max); + console.log('CTRL: self.min = %s', self.min); + console.log('CTRL: self.max = %s', self.max); + } + ]) ``` ```html
hello world
-
max={{vm.max}}
-
min={{vm.min}}
+
max={{ctrl.max}}
+
min={{ctrl.min}}
``` Note: You can also name the controller when you inject it into the link function and access directive attributes as properties of the controller. ```javascript // Alternative to above example - function linkFunc(scope, el, attr, vm) { + function linkFunc(scope, el, attr, self) { 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); + console.log('LINK: self.min = %s', self.min); + console.log('LINK: self.max = %s', self.max); } ``` -###### [Style [Y076](#style-y076)] + +### Use `bindToController` in directive definitions - Use `bindToController = true` when using `controller as` syntax with a directive when you want to bind the outer scope to the directive's controller's scope. @@ -1276,198 +1220,106 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ``` ```javascript + // my-example-directive.js 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; - console.log('CTRL: vm.min = %s', vm.min); - console.log('CTRL: vm.max = %s', vm.max); - } + .directive('myExample', [ + function myExample() { + var directive = { + restrict: 'EA', + templateUrl: 'app/feature/example.directive.html', + scope: { + max: '=' + }, + controller: ExampleController, + controllerAs: 'ctrl', + bindToController: true + }; + + return directive; + } + ]); +``` + +```javascript + // example-controller.js + angular + .module('app') + .controller('ExampleController', [ + function ExampleController() { + var self = this; + self.min = 3; + console.log('CTRL: self.min = %s', self.min); + console.log('CTRL: self.max = %s', self.max); + } + ]); ``` ```html
hello world
-
max={{vm.max}}
-
min={{vm.min}}
+
max={{ctrl.max}}
+
min={{ctrl.min}}
``` -**[Back to top](#table-of-contents)** - -## Resolving Promises for a Controller - -### Controller Activation Promises -###### [Style [Y080](#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](#style-y081) instead. - - ```javascript - /* avoid */ - function Avengers(dataservice) { - var vm = this; - vm.avengers = []; - vm.title = 'Avengers'; - - dataservice.getAvengers().then(function(data) { - vm.avengers = data; - return vm.avengers; - }); - } - ``` - - ```javascript - /* recommended */ - function Avengers(dataservice) { - var vm = this; - vm.avengers = []; - vm.title = 'Avengers'; - - activate(); - - //////////// - - function activate() { - return dataservice.getAvengers().then(function(data) { - vm.avengers = data; - return vm.avengers; - }); - } - } - ``` - -### Route Resolve Promises -###### [Style [Y081](#style-y081)] - - - When a controller depends on a promise to be resolved before the controller is activated, resolve those dependencies in the `$routeProvider` before the controller logic is executed. If you need to conditionally cancel a route before the controller is activated, use a route resolver. - - - Use a route resolve when you want to decide to cancel the route before ever transitioning to the View. - - *Why?*: A controller may require data before it loads. That data may come from a promise via a custom factory or [$http](https://docs.angularjs.org/api/ng/service/$http). Using a [route resolve](https://docs.angularjs.org/api/ngRoute/provider/$routeProvider) allows the promise to resolve before the controller logic executes, so it might take action based on that data from the promise. - - *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](#style-y080) instead. - - ```javascript - /* avoid */ - angular - .module('app') - .controller('Avengers', Avengers); - - function Avengers(movieService) { - var vm = this; - // unresolved - vm.movies; - // resolved asynchronously - movieService.getMovies().then(function(response) { - vm.movies = response.movies; - }); - } - ``` - - ```javascript - /* better */ - - // route-config.js - angular - .module('app') - .config(config); - - function config($routeProvider) { - $routeProvider - .when('/avengers', { - templateUrl: 'avengers.html', - controller: 'Avengers', - controllerAs: 'vm', - resolve: { - moviesPrepService: function(movieService) { - return movieService.getMovies(); - } - } - }); - } +**[Back to top](#table-of-contents)** - // avengers.js - angular - .module('app') - .controller('Avengers', Avengers); +## Resolving a Controller - Avengers.$inject = ['moviesPrepService']; - function Avengers(moviesPrepService) { - var vm = this; - vm.movies = moviesPrepService.movies; - } - ``` +### Controller Activation + - Note: The example below shows the route resolve points to a named function, which is easier to debug and easier to handle dependency injection. + - Resolve start-up logic for a controller in an `activate` function. - ```javascript - /* even better */ + *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. - // route-config.js - angular - .module('app') - .config(config); + *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. - function config($routeProvider) { - $routeProvider - .when('/avengers', { - templateUrl: 'avengers.html', - controller: 'Avengers', - controllerAs: 'vm', - resolve: { - moviesPrepService: moviesPrepService - } - }); - } + Note: If you need to conditionally cancel the route before you start use the controller, use a `canActive` approach introduced in `angular 1.4`. + + ```javascript + /* avoid */ + function Avengers(dataservice) { + var self = this; + self.avengers = []; + self.title = 'Avengers'; - function moviePrepService(movieService) { - return movieService.getMovies(); + dataservice.getAvengers().then(function(data) { + self.avengers = data; + return self.avengers; + }); } + ``` - // avengers.js - angular - .module('app') - .controller('Avengers', Avengers); + ```javascript + /* recommended */ + function Avengers(dataservice) { + var self = this; + self.avengers = []; + self.title = 'Avengers'; + + activate(); + + //////////// - Avengers.$inject = ['moviesPrepService']; - function Avengers(moviesPrepService) { - var vm = this; - vm.movies = moviesPrepService.movies; + function activate() { + return dataservice.getAvengers(function(err, data) { + self.avengers = data; + }); + } } ``` - 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). + +### Route Resolve Promises + + - Try to avoid usage of `$routeProvider` `resolve` promises, it leads to concepts mess and promis based logic on configurations step **[Back to top](#table-of-contents)** ## Manual Annotating for Dependency Injection ### UnSafe from Minification -###### [Style [Y090](#style-y090)] + - Avoid using the shortcut syntax of declaring dependencies without using a minification-safe approach. @@ -1491,39 +1343,15 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ``` ### Manually Identify Dependencies -###### [Style [Y091](#style-y091)] + - - Use `$inject` to manually identify your dependencies for Angular components. - - *Why?*: This technique mirrors the technique used by [`ng-annotate`](https://github.com/olov/ng-annotate), which I recommend for automating the creation of minification safe dependencies. If `ng-annotate` detects injection has already been made, it will not duplicate it. + - Use the inline array annotation ([preferred](https://docs.angularjs.org/guide/di)) + - **Always** use `ngStrictDi` with `ngApp` to avoid unexpected behaviour after minification *Why?*: This safeguards your dependencies from being vulnerable to minification issues when parameters may be mangled. For example, `common` and `dataservice` may become `a` or `b` and not be found by Angular. - *Why?*: Avoid creating in-line dependencies as long lists can be difficult to read in the array. Also it can be confusing that the array is a series of strings while the last item is the component's function. - ```javascript /* avoid */ - angular - .module('app') - .controller('Dashboard', - ['$location', '$routeParams', 'common', 'dataservice', - function Dashboard($location, $routeParams, common, dataservice) {} - ]); - ``` - - ```javascript - /* avoid */ - angular - .module('app') - .controller('Dashboard', - ['$location', '$routeParams', 'common', 'dataservice', Dashboard]); - - function Dashboard($location, $routeParams, common, dataservice) { - } - ``` - - ```javascript - /* recommended */ angular .module('app') .controller('Dashboard', Dashboard); @@ -1534,187 +1362,27 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see } ``` - Note: When your function is below a return statement the `$inject` may be unreachable (this may happen in a directive). You can solve this by moving the Controller outside of the directive. - - ```javascript - /* avoid */ - // inside a directive definition - function outer() { - var ddo = { - controller: DashboardPanelController, - controllerAs: 'vm' - }; - return ddo; - - DashboardPanelController.$inject = ['logger']; // Unreachable - function DashboardPanelController(logger) { - } - } - ``` - ```javascript /* recommended */ - // outside a directive definition - function outer() { - var ddo = { - controller: DashboardPanelController, - controllerAs: 'vm' - }; - return ddo; - } - - DashboardPanelController.$inject = ['logger']; - function DashboardPanelController(logger) { - } - ``` - -### Manually Identify Route Resolver Dependencies -###### [Style [Y092](#style-y092)] - - - Use `$inject` to manually identify your route resolver dependencies for Angular components. - - *Why?*: This technique breaks out the anonymous function for the route resolver, making it easier to read. - - *Why?*: An `$inject` statement can easily precede the resolver to handle making any dependencies minification safe. - - ```javascript - /* recommended */ - function config($routeProvider) { - $routeProvider - .when('/avengers', { - templateUrl: 'avengers.html', - controller: 'AvengersController', - controllerAs: 'vm', - resolve: { - moviesPrepService: moviePrepService - } - }); - } - - moviePrepService.$inject = ['movieService']; - function moviePrepService(movieService) { - return movieService.getMovies(); - } - ``` - -**[Back to top](#table-of-contents)** - -## Minification and Annotation - -### ng-annotate -###### [Style [Y100](#style-y100)] - - - Use [ng-annotate](//github.com/olov/ng-annotate) for [Gulp](http://gulpjs.com) or [Grunt](http://gruntjs.com) and comment functions that need automated dependency injection using `/** @ngInject */` - - *Why?*: This safeguards your code from any dependencies that may not be using minification-safe practices. - - *Why?*: [`ng-min`](https://github.com/btford/ngmin) is deprecated - - >I prefer Gulp as I feel it is easier to write, to read, and to debug. - - The following code is not using minification safe dependencies. - - ```javascript - angular - .module('app') - .controller('Avengers', Avengers); - - /* @ngInject */ - function Avengers(storageService, avengerService) { - var vm = this; - vm.heroSearch = ''; - vm.storeHero = storeHero; - - function storeHero() { - var hero = avengerService.find(vm.heroSearch); - storageService.save(hero.name, hero); - } - } - ``` - - When the above code is run through ng-annotate it will produce the following output with the `$inject` annotation and become minification-safe. - - ```javascript angular .module('app') - .controller('Avengers', Avengers); - - /* @ngInject */ - function Avengers(storageService, avengerService) { - var vm = this; - vm.heroSearch = ''; - vm.storeHero = storeHero; - - function storeHero() { - var hero = avengerService.find(vm.heroSearch); - storageService.save(hero.name, hero); - } - } - - Avengers.$inject = ['storageService', 'avengerService']; - ``` - - Note: If `ng-annotate` detects injection has already been made (e.g. `@ngInject` was detected), it will not duplicate the `$inject` code. - - Note: When using a route resolver you can prefix the resolver's function with `/* @ngInject */` and it will produce properly annotated code, keeping any injected dependencies minification safe. - - ```javascript - // Using @ngInject annotations - function config($routeProvider) { - $routeProvider - .when('/avengers', { - templateUrl: 'avengers.html', - controller: 'Avengers', - controllerAs: 'vm', - resolve: { /* @ngInject */ - moviesPrepService: function(movieService) { - return movieService.getMovies(); - } - } - }); - } + .controller('Dashboard', [ + '$location', '$routeParams', 'common', 'dataservice', + function Dashboard($location, $routeParams, common, dataservice) {} + ]); ``` - > Note: Starting from Angular 1.3 you can use the [`ngApp`](https://docs.angularjs.org/api/ng/directive/ngApp) directive's `ngStrictDi` parameter to detect any potentially missing magnification safe dependencies. When present the injector will be created in "strict-di" mode causing the application to fail to invoke functions which do not use explicit function annotation (these may not be minification safe). Debugging info will be logged to the console to help track down the offending code. I prefer to only use `ng-strict-di` for debugging purposes only. - `` - -### Use Gulp or Grunt for ng-annotate -###### [Style [Y101](#style-y101)] - - - Use [gulp-ng-annotate](https://www.npmjs.org/package/gulp-ng-annotate) or [grunt-ng-annotate](https://www.npmjs.org/package/grunt-ng-annotate) in an automated build task. Inject `/* @ngInject */` prior to any function that has dependencies. - - *Why?*: ng-annotate will catch most dependencies, but it sometimes requires hints using the `/* @ngInject */` syntax. - - The following code is an example of a gulp task using ngAnnotate - - ```javascript - gulp.task('js', ['jshint'], function() { - var source = pkg.paths.js; - - return gulp.src(source) - .pipe(sourcemaps.init()) - .pipe(concat('all.min.js', {newLine: ';'})) - // Annotate before uglify so the code get's min'd properly. - .pipe(ngAnnotate({ - // true helps add where @ngInject is not used. It infers. - // Doesn't work with resolve, so we must be explicit there - add: true - })) - .pipe(bytediff.start()) - .pipe(uglify({mangle: true})) - .pipe(bytediff.stop()) - .pipe(sourcemaps.write('./')) - .pipe(gulp.dest(pkg.paths.dev)); - }); - - ``` +**[Back to top](#table-of-contents)** +## Minification and Annotation +TODO + **[Back to top](#table-of-contents)** ## Exception Handling ### decorators -###### [Style [Y110](#style-y110)] + - Use a [decorator](https://docs.angularjs.org/api/auto/service/$provide#decorator), at config time using the [`$provide`](https://docs.angularjs.org/api/auto/service/$provide) service, on the [`$exceptionHandler`](https://docs.angularjs.org/api/ng/service/$exceptionHandler) service to perform custom actions when exceptions occur. @@ -1726,36 +1394,37 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see /* 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); - }; - } + .config([ + '$provide', + function exceptionConfig($provide) { + $provide.decorator('$exceptionHandler', [ + '$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 -###### [Style [Y111](#style-y111)] + - Create a factory that exposes an interface to catch and gracefully handle exceptions. @@ -1767,26 +1436,26 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see /* recommended */ angular .module('blocks.exception') - .factory('exception', exception); - - exception.$inject = ['logger']; - - function exception(logger) { - var service = { - catcher: catcher - }; - return service; - - function catcher(message) { - return function(reason) { - logger.error(message, reason); - }; - } - } + .factory('exception', [ + 'logger', + function exception(logger) { + function ExceptionLogger() {} + ExceptionLogger.prototype = { + catcher: catcher + }; + return new ExceptionLogger(); + + function catcher(message) { + return function(reason) { + logger.error(message, reason); + }; + } + } + ]); ``` ### Route Errors -###### [Style [Y112](#style-y112)] + - Handle and log all routing errors using [`$routeChangeError`](https://docs.angularjs.org/api/ngRoute/service/$route#$routeChangeError). @@ -1835,9 +1504,9 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ## Naming ### Naming Guidelines -###### [Style [Y120](#style-y120)] + - - Use consistent names for all components following a pattern that describes the component's feature then (optionally) its type. My recommended pattern is `feature.type.js`. There are 2 names for most assets: + - Use consistent names for all components following a pattern that describes the component's feature then its type. My 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`) @@ -1846,9 +1515,9 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see *Why?*: The naming conventions should simply help you find your code faster and make it easier to understand. ### Feature File Names -###### [Style [Y121](#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 `feature.type.js`. + - Use consistent names for all components following a pattern that describes the component's feature then its type. My recommended pattern is `feature.type.js`. *Why?*: Provides a consistent way to quickly identify components. @@ -1860,12 +1529,10 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see */ // Controllers - avengers.js avengers.controller.js avengersController.js // Services/Factories - logger.js logger.service.js loggerService.js ``` @@ -1900,20 +1567,9 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see avenger-profile.directive.js avenger-profile.directive.spec.js ``` - - 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`. - - ```javascript - /** - * recommended - */ - // Controllers - avengers.js - avengers.spec.js - ``` - + ### Test File Names -###### [Style [Y122](#style-y122)] + - Name test specifications similar to the component they test with a suffix of `spec`. @@ -1932,7 +1588,7 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ``` ### Controller Names -###### [Style [Y123](#style-y123)] + - Use consistent names for all controllers named after their feature. Use UpperCamelCase for controllers, as they are constructors. @@ -1945,18 +1601,19 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see * recommended */ - // avengers.controller.js + // hero-avengers.controller.js angular .module - .controller('HeroAvengersController', HeroAvengersController); - - function HeroAvengersController() { } + .controller('HeroAvengersController', [ + function HeroAvengersController() { } + ]); ``` ### Controller Name Suffix -###### [Style [Y124](#style-y124)] + - - Append the controller name with the suffix `Controller`. + - Append the controller name with the suffix `Controller` + - Don't use `Ctrl`, it is good only for tutorials *Why?*: The `Controller` suffix is more commonly used and is more explicitly descriptive. @@ -1968,15 +1625,16 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see // avengers.controller.js angular .module - .controller('AvengersController', AvengersController); - - function AvengersController() { } + .controller('AvengersController', [ + function AvengersController() { } + ]); ``` ### Factory Names -###### [Style [Y125](#style-y125)] + - Use consistent names for all factories named after their feature. Use camel-casing for services and factories. Avoid prefixing factories and services with `$`. + - Append the factory name with the suffix `Service` *Why?*: Provides a consistent way to quickly identify and reference factories. @@ -1990,13 +1648,15 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see // logger.service.js angular .module - .factory('logger', logger); + .factory('loggerService', [ + function loggerService() { } + ]); - function logger() { } + ``` ### Directive Component Names -###### [Style [Y126](#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). @@ -2010,15 +1670,14 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see // avenger-profile.directive.js angular .module - .directive('xxAvengerProfile', xxAvengerProfile); - - // usage is - - function xxAvengerProfile() { } + // usage is + .directive('xxAvengerProfile', [ + function xxAvengerProfile() { } + ]); ``` ### Modules -###### [Style [Y127](#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`. @@ -2027,7 +1686,7 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see *Why?*: Provides easy way to use task automation to load all module definitions first, then all other angular files (for bundling). ### Configuration -###### [Style [Y128](#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`. @@ -2036,7 +1695,7 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see *Why?*: Provides an identifiable place to set configuration for a module. ### Routes -###### [Style [Y129](#style-y129)] + - Separate route configuration into its own file. Examples might be `app.route.js` for the main module and `admin.route.js` for the `admin` module. Even in smaller apps I prefer this separation from the rest of the configuration. @@ -2044,7 +1703,7 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ## Application Structure LIFT Principle ### LIFT -###### [Style [Y140](#style-y140)] + - Structure your app such that you can `L`ocate your code quickly, `I`dentify the code at a glance, keep the `F`lattest structure you can, and `T`ry to stay DRY. The structure should follow these 4 basic guidelines. @@ -2058,16 +1717,16 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see 4. `T`ry to stay DRY (Don’t Repeat Yourself) or T-DRY ### Locate -###### [Style [Y141](#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 + /public + /assets + /components /avengers /blocks /exception @@ -2076,28 +1735,30 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see /dashboard /data /layout + /shared /widgets /content + /libs index.html .bower.json ``` ### Identify -###### [Style [Y142](#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](#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](#style-y144)] + - Be DRY, but don't go nuts and sacrifice readability. @@ -2108,21 +1769,23 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ## Application Structure ### Overall Guidelines -###### [Style [Y150](#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`). + - 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 `components`. 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 `components` folder. I didn't write them and I don't want them cluttering my app (`bower_components`, `scripts`, `libs`). Note: Find more details and reasoning behind the structure at [this original post on application structure](http://www.johnpapa.net/angular-app-structuring-guidelines/). + Note: Small and good article about [components oriented structure](https://scotch.io/tutorials/angularjs-best-practices-directory-structure) + ### Layout -###### [Style [Y151](#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](#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. @@ -2143,39 +1806,41 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see app.module.js app.config.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 - people.routes.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 - sessions.routes.js - session-detail.html - session-detail.controller.js + layouts/ + shell/ + shell.html + shell.controller.js + topnav/ + topnav.html + topnav.controller.js + people/ + people.routes.js + attendees/ + attendees.html + attendees.controller.js + speakers/ + speakers.html + speakers.controller.js + speaker-detail/ + speaker-detail.html + speaker-detail.controller.js + shared/ + user-profile/ + user-profile.directive.js + user-profile.directive.html + calendar/ + calendar.directive.js + calendar.directive.html + sessions/ + sessions.html + sessions.controller.js + sessions.routes.js + session-detail/ + session-detail.html + session-detail.controller.js ``` - ![Sample App Structure](https://raw.githubusercontent.com/johnpapa/angular-styleguide/master/assets/modularity-2.png) - - 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. + 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. ```javascript /* @@ -2221,21 +1886,21 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ## Modularity ### Many Small, Self Contained Modules -###### [Style [Y160](#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](#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?*: Angular 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](#style-y162)] + - Only put logic for pulling together the app in the application module. Leave features in their own modules. @@ -2244,7 +1909,7 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see *Why?*: The app module becomes a manifest that describes which modules help define the application. ### Feature Areas are Modules -###### [Style [Y163](#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). @@ -2255,14 +1920,14 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see *Why?*: Separating feature areas into modules makes it easier to test the modules in isolation and reuse code. ### Reusable Blocks are Modules -###### [Style [Y164](#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](#style-y165)] + - The application root module depends on the app specific feature modules and any shared or reusable modules. @@ -2285,7 +1950,7 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ## Startup Logic ### Configuration -###### [Style [Y170](#style-y170)] + - Inject code into [module configuration](https://docs.angularjs.org/guide/module#module-loading-dependencies) that must be configured before running the angular app. Ideal candidates include providers and constants. @@ -2294,30 +1959,28 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ```javascript angular .module('app') - .config(configure); + .config([ + 'routerHelperProvider', 'exceptionHandlerProvider', 'toastr', + function configure (routerHelperProvider, exceptionHandlerProvider, toastr) { + exceptionHandlerProvider.configure(config.appErrorPrefix); + configureStateHelper(); - configure.$inject = - ['routerHelperProvider', 'exceptionHandlerProvider', 'toastr']; + toastr.options.timeOut = 4000; + toastr.options.positionClass = 'toast-bottom-right'; - 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: ' - }); - } - } + function configureStateHelper() { + routerHelperProvider.configure({ + docTitle: 'NG-Modular: ' + }); + } + } + ]); ``` ### Run Blocks -###### [Style [Y171](#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](https://docs.angularjs.org/guide/module#module-loading-dependencies). @@ -2326,14 +1989,13 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ```javascript angular .module('app') - .run(runBlock); - - runBlock.$inject = ['authenticator', 'translator']; - - function runBlock(authenticator, translator) { - authenticator.initialize(); - translator.initialize(); - } + .run([ + 'authenticator', 'translator', + function runBlock(authenticator, translator) { + authenticator.initialize(); + translator.initialize(); + } + ]); ``` **[Back to top](#table-of-contents)** @@ -2341,14 +2003,14 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see ## Angular $ Wrapper Services ### $document and $window -###### [Style [Y180](#style-y180)] + - Use [`$document`](https://docs.angularjs.org/api/ng/service/$document) and [`$window`](https://docs.angularjs.org/api/ng/service/$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](#style-y181)] + - Use [`$timeout`](https://docs.angularjs.org/api/ng/service/$timeout) and [`$interval`](https://docs.angularjs.org/api/ng/service/$interval) instead of `setTimeout` and `setInterval` . @@ -2360,7 +2022,7 @@ While this guide explains the *what*, *why* and *how*, I find it helpful to see 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](#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. @@ -2387,16 +2049,14 @@ Unit testing helps maintain clean code, as such I included some of my recommenda ``` ### Testing Library -###### [Style [Y191](#style-y191)] + - - Use [Jasmine](http://jasmine.github.io/) or [Mocha](http://mochajs.org) for unit testing. + - Use [Mocha](http://mochajs.org) for unit testing. - *Why?*: Both Jasmine and Mocha are widely used in the Angular community. Both are stable, well maintained, and provide robust testing features. - - Note: When using Mocha, also consider choosing an assert library such as [Chai](http://chaijs.com). I prefer Mocha. + Note: When using Mocha, also consider choosing an assert library such as [Chai](http://chaijs.com). ### Test Runner -###### [Style [Y192](#style-y192)] + - Use [Karma](http://karma-runner.github.io) as a test runner. @@ -2404,12 +2064,12 @@ Unit testing helps maintain clean code, as such I included some of my recommenda *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](http://www.jetbrains.com/webstorm/) and [Visual Studio](http://visualstudiogallery.msdn.microsoft.com/02f47876-0e7a-4f6c-93f8-1af5d5189225). + *Why?*: Some IDE's are beginning to integrate with Karma, such as [WebStorm](http://www.jetbrains.com/webstorm/) - *Why?*: Karma works well with task automation leaders such as [Grunt](http://www.gruntjs.com) (with [grunt-karma](https://github.com/karma-runner/grunt-karma)) and [Gulp](http://www.gulpjs.com) (with [gulp-karma](https://github.com/lazd/gulp-karma)). + *Why?*: Karma works well with task automation leaders such as [Gulp](http://www.gulpjs.com) (with [gulp-karma](https://github.com/lazd/gulp-karma)). ### Stubbing and Spying -###### [Style [Y193](#style-y193)] + - Use [Sinon](http://sinonjs.org/) for stubbing and spying. @@ -2420,7 +2080,7 @@ Unit testing helps maintain clean code, as such I included some of my recommenda *Why?*: Sinon has descriptive messages when tests fail the assertions. ### Headless Browser -###### [Style [Y194](#style-y194)] + - Use [PhantomJS](http://phantomjs.org/) to run your tests on a server. @@ -2429,33 +2089,29 @@ Unit testing helps maintain clean code, as such I included some of my recommenda Note: You should still test on all browsers in your environment, as appropriate for your target audience. ### Code Analysis -###### [Style [Y195](#style-y195)] + - - Run JSHint on your tests. + - Run ESLint on your tests. - *Why?*: Tests are code. JSHint can help identify code quality issues that may cause the test to work improperly. + *Why?*: Tests are code. ESLint can help identify code quality issues that may cause the test to work improperly. -### Alleviate Globals for JSHint Rules on Tests -###### [Style [Y196](#style-y196)] +### Alleviate Globals for ESLint Rules on Tests + - Relax the rules on your test code to allow for common globals such as `describe` and `expect`. Relax the rules for expressions, as Mocha uses these. - *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. - - ```javascript - /* jshint -W117, -W030 */ - ``` - Or you can add the following to your JSHint Options file. + *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. + Add the following to your JSHint Options file. ```javascript "jasmine": true, - "mocha": true, + "mocha": true ``` ![Testing Tools](https://raw.githubusercontent.com/johnpapa/angular-styleguide/master/assets/testing-tools.png) ### Organizing Tests -###### [Style [Y197](#style-y197)] + - Place unit test files (specs) side-by-side with your client code. Place specs that cover server integration or test multiple components in a separate `tests` folder. @@ -2486,7 +2142,7 @@ Unit testing helps maintain clean code, as such I included some of my recommenda ## Animations ### Usage -###### [Style [Y210](#style-y210)] + - Use subtle [animations with Angular](https://docs.angularjs.org/guide/animations) to transition between states for views and primary visual elements. Include the [ngAnimate module](https://docs.angularjs.org/api/ngAnimate). The 3 keys are subtle, smooth, seamless. @@ -2495,14 +2151,14 @@ Unit testing helps maintain clean code, as such I included some of my recommenda *Why?*: Subtle animations can improve perceived performance as views transition. ### Sub Second -###### [Style [Y211](#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](#style-y212)] + - Use [animate.css](http://daneden.github.io/animate.css/) for conventional animations. @@ -2519,7 +2175,7 @@ Unit testing helps maintain clean code, as such I included some of my recommenda ## Comments ### jsDoc -###### [Style [Y220](#style-y220)] + - If planning to produce documentation, use [`jsDoc`](http://usejsdoc.org/) syntax to document function names, description, params and returns. Use `@namespace` and `@memberOf` to match your app structure. @@ -2568,175 +2224,25 @@ Unit testing helps maintain clean code, as such I included some of my recommenda **[Back to top](#table-of-contents)** -## JS Hint +## ESLint -### Use an Options File -###### [Style [Y230](#style-y230)] +### Use an options file - - 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](http://www.jshint.com/docs/) for details on the options. + - Use ESLint for linting and checking your coding styles of your JavaScript and be sure to customize the ESLint options file and include in source control. See the [ESLint docs](http://eslint.org/docs/rules/) for details on the options. *Why?*: Provides a first alert prior to committing any code to source control. *Why?*: Provides consistency across your team. - ```javascript - { - "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](#table-of-contents)** - -## JSCS - -### Use an Options File -###### [Style [Y235](#style-y235)] + Latest eslint config you can find [here](https://github.com/valor-software/valor-style-guides/blob/master/.eslintrc) - - Use JSCS for checking your coding styles your JavaScript and be sure to customize the JSCS options file and include in source control. See the [JSCS docs](http://www.jscs.info) for details on the options. - - *Why?*: Provides a first alert prior to committing any code to source control. - - *Why?*: Provides consistency across your team. - - ```javascript - { - "excludeFiles": ["node_modules/**", "bower_components/**"], - - "requireCurlyBraces": [ - "if", - "else", - "for", - "while", - "do", - "try", - "catch" - ], - "requireOperatorBeforeLineBreak": true, - "requireCamelCaseOrUpperCaseIdentifiers": true, - "maximumLineLength": { - "value": 100, - "allowComments": true, - "allowRegex": true - }, - "validateIndentation": 4, - "validateQuoteMarks": "'", - - "disallowMultipleLineStrings": true, - "disallowMixedSpacesAndTabs": true, - "disallowTrailingWhitespace": true, - "disallowSpaceAfterPrefixUnaryOperators": true, - "disallowMultipleVarDecl": null, - - "requireSpaceAfterKeywords": [ - "if", - "else", - "for", - "while", - "do", - "switch", - "return", - "try", - "catch" - ], - "requireSpaceBeforeBinaryOperators": [ - "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", - "&=", "|=", "^=", "+=", - - "+", "-", "*", "/", "%", "<<", ">>", ">>>", "&", - "|", "^", "&&", "||", "===", "==", ">=", - "<=", "<", ">", "!=", "!==" - ], - "requireSpaceAfterBinaryOperators": true, - "requireSpacesInConditionalExpression": true, - "requireSpaceBeforeBlockStatements": true, - "requireLineFeedAtFileEnd": true, - "disallowSpacesInsideObjectBrackets": "all", - "disallowSpacesInsideArrayBrackets": "all", - "disallowSpacesInsideParentheses": true, - - "validateJSDoc": { - "checkParamNames": true, - "requireParamTypes": true - }, - - "disallowMultipleLineBreaks": true, - - "disallowCommaBeforeLineBreak": null, - "disallowDanglingUnderscores": null, - "disallowEmptyBlocks": null, - "disallowMultipleLineStrings": null, - "disallowTrailingComma": null, - "requireCommaBeforeLineBreak": null, - "requireDotNotation": null, - "requireMultipleVarDecl": null, - "requireParenthesesAroundIIFE": true - } - ``` **[Back to top](#table-of-contents)** ## Constants ### Vendor Globals -###### [Style [Y240](#style-y240)] + - Create an Angular Constant for vendor libraries' global variables. @@ -2745,18 +2251,14 @@ Unit testing helps maintain clean code, as such I included some of my recommenda ```javascript // constants.js - /* global toastr:false, moment:false */ - (function() { - 'use strict'; - - angular - .module('app.core') - .constant('toastr', toastr) - .constant('moment', moment); - })(); + /* eslint-env toastr, moment */ + angular + .module('app.core') + .constant('toastr', toastr) + .constant('moment', moment); ``` -###### [Style [Y241](#style-y241)] + - Use constants for values that do not change and do not come from another service. When constants are used only for a module that may be reused in multiple applications, place constants in a file per module named after the module. Until this is required, keep constants in the main module in a `constants.js` file. @@ -2783,41 +2285,14 @@ Unit testing helps maintain clean code, as such I included some of my recommenda **[Back to top](#table-of-contents)** -## File Templates and Snippets + -### Sublime Text -###### [Style [Y250](#style-y250)] - - - Angular snippets that follow these styles and guidelines. - - - Download the [Sublime Angular snippets](assets/sublime-angular-snippets?raw=true) - - Place it in your Packages folder - - Restart Sublime - - In a JavaScript file type these commands followed by a `TAB` + + - ```javascript - ngcontroller // creates an Angular controller - ngdirective // creates an Angular directive - ngfactory // creates an Angular factory - ngmodule // creates an Angular module - ngservice // creates an Angular service - ngfilter // creates an Angular filter - ``` - -### Visual Studio -###### [Style [Y251](#style-y251)] - - - Angular file templates that follow these styles and guidelines can be found at [SideWaffle](http://www.sidewaffle.com) - - - Download the [SideWaffle](http://www.sidewaffle.com) Visual Studio extension (vsix file) - - Run the vsix file - - Restart Visual Studio - -### WebStorm -###### [Style [Y252](#style-y252)] - - - Angular snippets and file templates that follow these styles and guidelines. You can import them into your WebStorm settings: + + ## 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-y270)] + - Use the [AngularUI Router](http://angular-ui.github.io/ui-router/) for client-side routing. @@ -2925,7 +2319,7 @@ Client-side routing is important for creating a navigation flow between views an *Why?*: The syntax is quite similar to the Angular router and is easy to migrate to UI Router. -###### [Style [Y271](#style-y271)] + - Define routes for views in the module where they exist. Each module should contain the routes for the views in the module. @@ -2938,11 +2332,9 @@ Client-side routing is important for creating a navigation flow between views an **[Back to top](#table-of-contents)** ## Task Automation -Use [Gulp](http://gulpjs.com) or [Grunt](http://gruntjs.com) 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](http://jpapa.me/gulpps) +Use [Gulp](http://gulpjs.com) for creating automated tasks. -###### [Style [Y400](#style-y400)] + - Use task automation to list module definition files `*.module.js` before all other application JavaScript files. @@ -2964,7 +2356,7 @@ Use [Gulp](http://gulpjs.com) or [Grunt](http://gruntjs.com) for creating automa ## Filters -###### [Style [Y420](#style-y420)] + - Avoid using filters for scanning all properties of a complex object graph. Use filters for select properties. @@ -2992,6 +2384,7 @@ _tldr; Use this guide. Attributions are appreciated._ ### Copyright +Copyright (c) 2015 [valorkin](https://github.com/valorkin) Copyright (c) 2014-2015 [John Papa](http://johnpapa.net) ### (The MIT License)