├── LICENSE ├── README.md ├── README.original.md ├── assets ├── above-the-fold-1.png ├── above-the-fold-2.png ├── brackets-angular-snippets.yaml ├── modularity-1.png ├── modularity-2.png ├── ng-clean-code-banner.png ├── sublime-angular-snippets │ ├── angular.controller.sublime-snippet │ ├── angular.directive.sublime-snippet │ ├── angular.factory.sublime-snippet │ ├── angular.filter.sublime-snippet │ ├── angular.module.sublime-snippet │ └── angular.service.sublime-snippet ├── testing-tools.png ├── vim-angular-snippets │ ├── angular.controller.snip │ ├── angular.directive.snip │ ├── angular.factory.snip │ ├── angular.filter.snip │ ├── angular.module.snip │ └── angular.service.snip ├── vim-angular-ultisnips │ ├── javascript_angular.controller.snippets │ ├── javascript_angular.directive.snippets │ ├── javascript_angular.factory.snippets │ ├── javascript_angular.filter.snippets │ ├── javascript_angular.module.snippets │ └── javascript_angular.service.snippets ├── vscode-snippets │ ├── javascript.json │ └── typescript.json └── webstorm-angular-file-template.settings.jar └── i18n ├── README.md ├── de-DE.md ├── es-ES.md ├── fr-FR.md ├── it-IT.md ├── ja-JP.md ├── mk-MK.md ├── pt-BR.md ├── ru-RU.md └── zh-CN.md /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 John Papa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular Style Guide ES2015/ES6 2 | 3 | This is an ES2015/ES6 fork of the popular Angular 1.x Style Guide by John Papa. It was originally written for use with generator-gulp-angular and Babel, and things that do not apply in that circumstance have been removed. 4 | 5 | **Note:** generator-gulp-angular has been deprecated, but the people working on it are now working on [FountainJS](http://fountainjs.io/), which I highly recommend as a starting point. Just make sure to choose Angular 1.x and Babel to stay in line with this guide. 6 | 7 | Please be advised that all examples will not be copy/paste working examples. In instances where classes are imported, 8 | it is expected that the imported class was defined correctly, in another file, and imported in. 9 | 10 | ## Table of Contents 11 | 12 | 1. [Single Responsibility](#single-responsibility) 13 | 1. [Modules](#modules) 14 | 1. [Controllers](#controllers) 15 | 1. [Factories](#factories) 16 | 1. [Data Services](#data-services) 17 | 1. [Directives](#directives) 18 | 1. [Components](#components) 19 | 1. [Resolving Promises](#route-resolve-promises) 20 | 1. [Minification and Annotation](#minification-and-annotation) 21 | 1. [Exception Handling](#exception-handling) 22 | 1. [Naming](#naming) 23 | 1. [Application Structure LIFT Principle](#application-structure-lift-principle) 24 | 1. [Application Structure](#application-structure) 25 | 1. [Modularity](#modularity) 26 | 1. [Startup Logic](#startup-logic) 27 | 1. [Angular $ Wrapper Services](#angular--wrapper-services) 28 | 1. [Testing](#testing) 29 | 1. [Animations](#animations) 30 | 1. [Comments](#comments) 31 | 1. [ESLint](#eslint) 32 | 1. [Constants](#constants) 33 | 1. [File Templates and Snippets](#file-templates-and-snippets) 34 | 1. [Yeoman Generator](#yeoman-generator) 35 | 1. [Routing](#routing) 36 | 1. [Task Automation](#task-automation) 37 | 1. [Filters](#filters) 38 | 1. [Angular Docs](#angular-docs) 39 | 1. [Contributing](#contributing) 40 | 1. [License](#license) 41 | 42 | ## Single Responsibility 43 | 44 | ### Rule of 1 45 | ###### [Style [Y001](#style-y001)] 46 | 47 | - Define 1 module per file. 48 | 49 | The following example defines the `app` module and its dependencies, defines a controller, and defines a factory all in the same file. 50 | 51 | ```js 52 | /* avoid */ 53 | 54 | class SomeController { 55 | constructor() { } 56 | } 57 | 58 | class someFactory{ 59 | constructor() { } 60 | } 61 | 62 | angular 63 | .module('app', ['ngRoute']) 64 | .controller('SomeController', SomeController) 65 | .factory('someFactory', someFactory); 66 | ``` 67 | 68 | The same entities are now separated into their own files. 69 | 70 | ```js 71 | /* recommended */ 72 | 73 | // app.module.js 74 | import { SomeController } from './some.controller'; 75 | import { someFactory } from './some.factory'; 76 | 77 | angular 78 | .module('app', ['ngRoute']) 79 | .controller('SomeController', SomeController) 80 | .factory('someFactory', someFactory); 81 | ``` 82 | 83 | ```javascript 84 | /* recommended */ 85 | 86 | // some.controller.js 87 | class SomeController{ 88 | constructor() { } 89 | } 90 | ``` 91 | 92 | ```javascript 93 | /* recommended */ 94 | 95 | // some.factory.js 96 | class someFactory{ 97 | constructor() { } 98 | } 99 | ``` 100 | 101 | **[Back to top](#table-of-contents)** 102 | 103 | ## Modules 104 | 105 | ### Avoid Naming Collisions 106 | ###### [Style [Y020](#style-y020)] 107 | 108 | - Use unique naming conventions with separators for sub-modules. 109 | 110 | *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`. 111 | 112 | ### Definitions (aka Setters) 113 | ###### [Style [Y021](#style-y021)] 114 | 115 | - Declare modules without a variable using the setter syntax. 116 | 117 | *Why?*: With 1 entity per file, there is rarely a need to introduce a variable for the module. 118 | 119 | ```javascript 120 | /* avoid */ 121 | const app = angular.module('app', [ 122 | 'ngAnimate', 123 | 'ngRoute', 124 | 'app.shared', 125 | 'app.dashboard' 126 | ]); 127 | ``` 128 | 129 | Instead use the simple setter syntax. 130 | 131 | ```javascript 132 | /* recommended */ 133 | angular 134 | .module('app', [ 135 | 'ngAnimate', 136 | 'ngRoute', 137 | 'app.shared', 138 | 'app.dashboard' 139 | ]); 140 | ``` 141 | 142 | ### Getters 143 | ###### [Style [Y022](#style-y022)] 144 | 145 | - When using a module, avoid unnecessarily using variables and instead use chaining with the getter syntax. 146 | 147 | *Why?*: This produces more readable code and avoids variable collisions or leaks. 148 | 149 | ```javascript 150 | /* avoid */ 151 | import { SomeController } from './some.controller'; 152 | 153 | const app = angular.module('app'); 154 | app.controller('SomeController', SomeController); 155 | ``` 156 | 157 | ```javascript 158 | /* recommended */ 159 | import { SomeController } from './some.controller'; 160 | 161 | angular 162 | .module('app') 163 | .controller('SomeController', SomeController); 164 | ``` 165 | 166 | ### Setting vs Getting 167 | ###### [Style [Y023](#style-y023)] 168 | 169 | - Only set once and get for all other instances. 170 | 171 | *Why?*: A module should only be created once, then retrieved from that point and after. 172 | 173 | ```javascript 174 | /* recommended */ 175 | 176 | // to set a module 177 | angular.module('app', []); 178 | 179 | // to get a module 180 | angular.module('app'); 181 | ``` 182 | 183 | ### Named vs Anonymous Functions 184 | ###### [Style [Y024](#style-y024)] 185 | 186 | - Use named functions instead of passing an anonymous function in as a callback. 187 | 188 | *Why?*: This produces more readable code, is much easier to debug, and reduces the amount of nested callback code. 189 | 190 | ```javascript 191 | /* avoid */ 192 | angular 193 | .module('app') 194 | .controller('Dashboard', () => { }) 195 | ``` 196 | 197 | ```javascript 198 | /* recommended */ 199 | 200 | // dashboard.controller.js 201 | 202 | class Dashboard { 203 | constructor() { } 204 | } 205 | 206 | // index.module.js 207 | import { Dashboard } from './dashboard.controller'; 208 | 209 | angular 210 | .module('app') 211 | .controller('Dashboard', Dashboard); 212 | ``` 213 | 214 | **[Back to top](#table-of-contents)** 215 | 216 | ## Controllers 217 | 218 | ### controllerAs View Syntax 219 | ###### [Style [Y030](#style-y030)] 220 | 221 | - 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. 222 | 223 | *Why?*: Controllers are constructed, "newed" up, and provide a single new instance, and the `controllerAs` syntax is closer to that of a JavaScript constructor than the `classic $scope syntax`. 224 | 225 | *Why?*: It promotes the use of binding to a "dotted" object in the View (e.g. `customer.name` instead of `name`), which is more contextual, easier to read, and avoids any reference issues that may occur without "dotting". 226 | 227 | *Why?*: Helps avoid using `$parent` calls in Views with nested controllers. 228 | 229 | ```html 230 | 231 |
232 | {{name}} 233 |
234 | ``` 235 | 236 | ```html 237 | 238 |
239 | {{customer.name}} 240 |
241 | ``` 242 | 243 | controllerAs can also be used in the router like so: 244 | 245 | ```js 246 | .when('/dropbox', { 247 | templateUrl: 'views/dropbox.html', 248 | controller: 'DropboxCtrl', 249 | controllerAs: 'dropbox' 250 | }) 251 | ``` 252 | 253 | ### controllerAs Controller Syntax 254 | ###### [Style [Y031](#style-y031)] 255 | 256 | - Use the `controllerAs` syntax over the `classic controller with $scope` syntax. 257 | 258 | - The `controllerAs` syntax uses `this` inside controllers which gets bound to `$scope` 259 | 260 | *Why?*: `controllerAs` is syntactic sugar over `$scope`. You can still bind to the View and still access `$scope` methods. 261 | 262 | *Why?*: Helps avoid the temptation of using `$scope` methods inside a controller when it may otherwise be better to avoid them or move the method to a factory, and reference them from the controller. Consider using `$scope` in a controller only when needed. For example when publishing and subscribing events using [`$emit`](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$emit), [`$broadcast`](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$broadcast), or [`$on`](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$on) consider moving these uses to a factory and invoke from the controller. 263 | 264 | ```javascript 265 | /* avoid */ 266 | class Customer { 267 | constructor($scope) { 268 | $scope.name = {}; 269 | $scope.sendMessage = function() { }; 270 | } 271 | } 272 | ``` 273 | 274 | ```javascript 275 | /* recommended - but see next section */ 276 | class Customer { 277 | constructor() { 278 | this.name = {}; 279 | } 280 | sendMessage(){ } 281 | } 282 | ``` 283 | 284 | ### controllerAs with fat arrows 285 | ###### [Style [Y032](#style-y032)] 286 | 287 | - Using a capture variable for `this` when using the `controllerAs` syntax, is not necessary with ES6. You can simply use a fat arrow to automatically reference the correct `this` context 288 | 289 | 290 | ```javascript 291 | /* avoid */ 292 | let self = this; 293 | () => { 294 | self.foo = 'bar'; 295 | } 296 | ``` 297 | 298 | ```javascript 299 | /* recommended */ 300 | () => { 301 | this.foo = 'bar'; 302 | } 303 | ``` 304 | 305 | ### Defer Controller Logic to Services 306 | ###### [Style [Y035](#style-y035)] 307 | 308 | - Defer logic in a controller by delegating to services and factories. 309 | 310 | *Why?*: Logic may be reused by multiple controllers when placed within a service and exposed via a function. 311 | 312 | *Why?*: Logic in a service can more easily be isolated in a unit test, while the calling logic in the controller can be easily mocked. 313 | 314 | *Why?*: Removes dependencies and hides implementation details from the controller. 315 | 316 | *Why?*: Keeps the controller slim, trim, and focused. 317 | 318 | ```javascript 319 | 320 | /* avoid */ 321 | class Order { 322 | constructor($http, $q, config, userInfo) { 323 | this.isCreditOk = false; 324 | this.total = 0; 325 | } 326 | checkCredit() { 327 | let settings = {}; 328 | // Get the credit service base URL from config 329 | // Set credit service required headers 330 | // Prepare URL query string or data object with request data 331 | // Add user-identifying info so service gets the right credit limit for this user. 332 | // Use JSONP for this browser if it doesn't support CORS 333 | return $http.get(settings) 334 | .then((data) => { 335 | // Unpack JSON data in the response object 336 | // to find maxRemainingAmount 337 | this.isCreditOk = this.total <= maxRemainingAmount 338 | }) 339 | .catch((error) => { 340 | // Interpret error 341 | // Cope w/ timeout? retry? try alternate service? 342 | // Re-reject with appropriate error for a user to see 343 | }); 344 | }; 345 | } 346 | ``` 347 | 348 | ```javascript 349 | /* recommended */ 350 | class Order { 351 | constructor (creditService) { 352 | this.isCreditOk; 353 | this.total = 0; 354 | } 355 | checkCredit() { 356 | return creditService.isOrderTotalOk(this.total) 357 | .then((isOk) => { this.isCreditOk = isOk; }) 358 | .catch(showError); 359 | } 360 | } 361 | ``` 362 | 363 | ### Keep Controllers Focused 364 | ###### [Style [Y037](#style-y037)] 365 | 366 | - 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. 367 | 368 | *Why?*: Reusing controllers with several views is brittle and good end-to-end (e2e) test coverage is required to ensure stability across large applications. 369 | 370 | ### Assigning Controllers 371 | ###### [Style [Y038](#style-y038)] 372 | 373 | - When a controller must be paired with a view and either entity may be re-used by other controllers or views, define controllers along with their routes. 374 | 375 | Note: If a View is loaded via another means besides a route, then use the `ng-controller="AvengersController as avengers"` syntax. 376 | 377 | *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. 378 | 379 | ```javascript 380 | /* avoid - when using with a route and dynamic pairing is desired */ 381 | 382 | // route-config.js 383 | angular 384 | .module('app') 385 | .config(config); 386 | 387 | function config($routeProvider) { 388 | $routeProvider 389 | .when('/avengers', { 390 | templateUrl: 'avengers.html' 391 | }); 392 | } 393 | ``` 394 | 395 | ```html 396 | 397 |
398 |
399 | ``` 400 | 401 | ```javascript 402 | /* recommended */ 403 | 404 | // route-config.js 405 | angular 406 | .module('app') 407 | .config(config); 408 | 409 | function config($routeProvider) { 410 | $routeProvider 411 | .when('/avengers', { 412 | templateUrl: 'avengers.html', 413 | controller: 'AvengersController', 414 | controllerAs: 'avengers' 415 | }); 416 | } 417 | ``` 418 | 419 | ```html 420 | 421 |
422 |
423 | ``` 424 | 425 | **[Back to top](#table-of-contents)** 426 | 427 | 428 | ## Factories 429 | 430 | ### Single Responsibility 431 | ###### [Style [Y050](#style-y050)] 432 | 433 | - 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. 434 | 435 | ### Singletons 436 | ###### [Style [Y051](#style-y051)] 437 | 438 | - Factories are singletons and return an object that contains the members of the service. 439 | 440 | Note: [All Angular services are singletons](https://docs.angularjs.org/guide/services). 441 | 442 | **[Back to top](#table-of-contents)** 443 | 444 | ## Data Services 445 | 446 | ### Separate Data Calls 447 | ###### [Style [Y060](#style-y060)] 448 | 449 | - 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. 450 | 451 | *Why?*: The controller's responsibility is for the presentation and gathering of information for the view. It should not care how it gets the data, just that it knows who to ask for it. Separating the data services moves the logic on how to get it to the data service, and lets the controller be simpler and more focused on the view. 452 | 453 | *Why?*: This makes it easier to test (mock or real) the data calls when testing a controller that uses a data service. 454 | 455 | *Why?*: Data service implementation may have very specific code to handle the data repository. This may include headers, how to talk to the data, or other services such as `$http`. Separating the logic into a data service encapsulates this logic in a single place hiding the implementation from the outside consumers (perhaps a controller), also making it easier to change the implementation. 456 | 457 | Note: The following ES6 factory definition uses the `new` operator to instantiate the factory function, and injects the desired services used internally. 458 | 459 | ES6: With Angular 1.x and ES6, use of factories will decrease and `services` should be used moving forward (see angular [services](#data-services)) 460 | 461 | ```javascript 462 | /* recommended */ 463 | 464 | // dataservice factory 465 | angular.module('app.core') 466 | .factory('dataservice', ['$http', 'logger', ($http, logger) 467 | => new Dataservice($http, logger)]); 468 | 469 | class Dataservice { 470 | constructor($http, logger) { 471 | this.$http = $http; 472 | this.logger = logger; 473 | } 474 | getAvengers() { 475 | return this.$http.get('/api/maa') 476 | .then(getAvengersComplete) 477 | .catch(getAvengersFailed); 478 | 479 | getAvengersComplete(response) => { 480 | return response.data.results; 481 | } 482 | 483 | getAvengersFailed(error) => { 484 | this.logger.error('XHR Failed for getAvengers.' + error.data); 485 | } 486 | } 487 | } 488 | ``` 489 | 490 | ### Return a Promise from Data Calls 491 | ###### [Style [Y061](#style-y061)] 492 | 493 | - When calling a data service that returns a promise such as `$http`, return a promise in your calling function too. 494 | 495 | *Why?*: You can chain the promises together and take further action after the data call completes and resolves or rejects the promise. 496 | 497 | ```javascript 498 | /* recommended */ 499 | 500 | activate() { 501 | /** 502 | * Step 1 503 | * Ask the getAvengers function for the 504 | * avenger data and wait for the promise 505 | */ 506 | return getAvengers().then(() => { 507 | /** 508 | * Step 4 509 | * Perform an action on resolve of final promise 510 | */ 511 | logger.info('Activated Avengers View'); 512 | }); 513 | } 514 | 515 | getAvengers() { 516 | /** 517 | * Step 2 518 | * Ask the data service for the data and wait 519 | * for the promise 520 | */ 521 | return dataservice.getAvengers() 522 | .then((data) => { 523 | /** 524 | * Step 3 525 | * set the data and resolve the promise 526 | */ 527 | this.avengers = data; 528 | return this.avengers; 529 | }); 530 | } 531 | ``` 532 | 533 | **[Back to top](#table-of-contents)** 534 | 535 | ## Directives 536 | ### Limit 1 Per File 537 | ###### [Style [Y070](#style-y070)] 538 | 539 | - Create one directive per file. Name the file for the directive. 540 | 541 | *Why?*: It is easy to mash all the directives in one file, but difficult to then break those out so some are shared across apps, some across modules, some just for one module. 542 | 543 | *Why?*: One directive per file is easy to maintain. 544 | 545 | > Note: "**Best Practice**: Directives should clean up after themselves. You can use `element.on('$destroy', ...)` or `scope.$on('$destroy', ...)` to run a clean-up function when the directive is removed" ... from the Angular documentation. 546 | 547 | ```javascript 548 | /* avoid */ 549 | /* directives.js */ 550 | 551 | class orderCalendarRange { 552 | /* implementation details */ 553 | } 554 | 555 | class salesCustomerInfo { 556 | /* implementation details */ 557 | } 558 | 559 | class sharedSpinner { 560 | /* implementation details */ 561 | } 562 | ``` 563 | 564 | ```javascript 565 | /* recommended */ 566 | 567 | /* calendarRange.directive.js */ 568 | 569 | /** 570 | * @desc order directive that is specific to the order module at a company named Acme 571 | * @example
572 | */ 573 | 574 | class orderCalendarRange { 575 | /* implementation details */ 576 | } 577 | ``` 578 | 579 | ```javascript 580 | /* recommended */ 581 | 582 | /* customerInfo.directive.js */ 583 | 584 | /** 585 | * @desc sales directive that can be used anywhere across the sales app at a company named Acme 586 | * @example
587 | */ 588 | 589 | class salesCustomerInfo { 590 | /* implementation details */ 591 | } 592 | ``` 593 | 594 | ```javascript 595 | /* recommended */ 596 | 597 | /* spinner.directive.js */ 598 | 599 | /** 600 | * @desc spinner directive that can be used anywhere across apps at a company named Acme 601 | * @example
602 | */ 603 | 604 | class sharedSpinner { 605 | /* implementation details */ 606 | } 607 | ``` 608 | 609 | 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. 610 | 611 | ### Manipulate DOM in a Directive 612 | ###### [Style [Y072](#style-y072)] 613 | 614 | - 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. 615 | 616 | *Why?*: DOM manipulation can be difficult to test, debug, and there are often better ways (e.g. CSS, animations, templates) 617 | 618 | ### Provide a Unique Directive Prefix 619 | ###### [Style [Y073](#style-y073)] 620 | 621 | - Provide a short, unique and descriptive directive prefix such as `acmeSalesCustomerInfo` which would be declared in HTML as `acme-sales-customer-info`. 622 | 623 | *Why?*: The unique short prefix identifies the directive's context and origin. For example a prefix of `cc-` may indicate that the directive is part of a CodeCamper app while `acme-` may indicate a directive for the Acme company. 624 | 625 | 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/). 626 | 627 | ### Restrict to Elements and Attributes 628 | ###### [Style [Y074](#style-y074)] 629 | 630 | - 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. 631 | 632 | *Why?*: It makes sense. 633 | 634 | *Why?*: While we can allow the directive to be used as a class, if the directive is truly acting as an element it makes more sense as an element or at least as an attribute. 635 | 636 | Note: EA is the default for Angular 1.3 + 637 | 638 | ```html 639 | 640 |
641 | ``` 642 | 643 | ```javascript 644 | /* avoid */ 645 | 646 | class myCalendarRange { 647 | constructor() { 648 | this.link = this.linkFunc; 649 | this.templateUrl = '/template/is/located/here.html'; 650 | this.restrict = 'C'; 651 | } 652 | linkFunc(scope, element, attrs) { 653 | /* */ 654 | } 655 | } 656 | ``` 657 | 658 | ```html 659 | 660 | 661 |
662 | ``` 663 | 664 | ```javascript 665 | /* recommended */ 666 | 667 | class myCalendarRange { 668 | constructor() { 669 | this.link = this.linkFunc; 670 | this.templateUrl = '/template/is/located/here.html'; 671 | this.restrict = 'EA'; 672 | } 673 | linkFunc(scope, element, attrs) { 674 | /* */ 675 | } 676 | } 677 | ``` 678 | 679 | ### Directives and ControllerAs 680 | ###### [Style [Y075](#style-y075)] 681 | 682 | - Use `controller as` syntax with a directive to be consistent with using `controller as` with view and controller pairings. 683 | 684 | *Why?*: It makes sense and it's not difficult. 685 | 686 | Note: The directive below demonstrates some of the ways you can use scope inside of link and directive controllers, using controllerAs. I in-lined the template just to keep it all in one place. 687 | 688 | Note: Note that the directive's controller is outside the directive's closure. This style eliminates issues where the injection gets created as unreachable code after a `return`. 689 | 690 | ```html 691 |
692 | ``` 693 | 694 | ```js 695 | //myExample.directive.js 696 | class ExampleController { 697 | constructor($scope) { 698 | // Injecting $scope just for comparison 699 | 700 | this.min = 3; 701 | 702 | console.log('CTRL: $scope.example.min = %s', $scope.example.min); 703 | console.log('CTRL: $scope.example.max = %s', $scope.example.max); 704 | console.log('CTRL: this.min = %s', this.min); 705 | console.log('CTRL: this.max = %s', this.max); 706 | } 707 | } 708 | 709 | class myExample { 710 | constructor() { 711 | this.restrict = 'EA'; 712 | this.templateUrl = 'app/feature/example.directive.html'; 713 | this.scope = { 714 | max: '=' 715 | }; 716 | this.link = this.linkFunc; 717 | this.controller = ExampleController; 718 | this.controllerAs = 'example'; 719 | this.bindToController = true; // because the scope is isolated 720 | } 721 | linkFunc(scope, el, attr, ctrl) { 722 | console.log('LINK: scope.min = %s *** should be undefined', scope.min); 723 | console.log('LINK: scope.max = %s *** should be undefined', scope.max); 724 | console.log('LINK: scope.example.min = %s', scope.example.min); 725 | console.log('LINK: scope.example.max = %s', scope.example.max); 726 | } 727 | } 728 | ``` 729 | 730 | ```html 731 | 732 |
hello world
733 |
max={{example.max}}
734 |
min={{example.min}}
735 | ``` 736 | 737 | Note: You can also name the controller when you inject it into the link function and access directive attributes as properties of the controller. 738 | 739 | ```javascript 740 | // Alternative to above example 741 | linkFunc(scope, el, attr, example) { 742 | console.log('LINK: scope.min = %s *** should be undefined', scope.min); 743 | console.log('LINK: scope.max = %s *** should be undefined', scope.max); 744 | console.log('LINK: example.min = %s', example.min); 745 | console.log('LINK: example.max = %s', example.max); 746 | } 747 | ``` 748 | 749 | ###### [Style [Y076](#style-y076)] 750 | 751 | - 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. 752 | 753 | *Why?*: It makes it easy to bind outer scope to the directive's controller scope. 754 | 755 | Note: `bindToController` was introduced in Angular 1.3.0. 756 | 757 | ```html 758 |
759 | ``` 760 | 761 | ```javascript 762 | //myExample.directive.js 763 | class ExampleController { 764 | constructor() { 765 | this.min = 3; 766 | console.log('CTRL: this.min = %s', this.min); 767 | console.log('CTRL: this.max = %s', this.max); 768 | } 769 | } 770 | 771 | class myExample { 772 | constructor() { 773 | this.restrict = 'EA'; 774 | this.templateUrl = 'app/feature/example.directive.html'; 775 | this.scope = { 776 | max: '=' 777 | }; 778 | this.link = this.linkFunc; 779 | this.controller = ExampleController; 780 | this.controllerAs = 'example'; 781 | this.bindToController = true; 782 | } 783 | } 784 | ``` 785 | 786 | ```html 787 | 788 |
hello world
789 |
max={{example.max}}
790 |
min={{example.min}}
791 | ``` 792 | 793 | **[Back to top](#table-of-contents)** 794 | 795 | ### Route Resolve Promises 796 | ###### [Style [Y081](#style-y081)] 797 | 798 | - 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. 799 | 800 | - Use a route resolve when you want to decide to cancel the route before ever transitioning to the View. 801 | 802 | *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. 803 | 804 | *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`) 805 | 806 | 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. 807 | 808 | ```javascript 809 | /* avoid */ 810 | 811 | class AvengersController { 812 | constructor(movieService) { 813 | // unresolved 814 | this.movies; 815 | // resolved asynchronously 816 | movieService.getMovies().then((response) => { 817 | this.movies = response.movies; 818 | }); 819 | } 820 | } 821 | ``` 822 | 823 | ```javascript 824 | /* better */ 825 | 826 | // route-config.js 827 | 828 | function config($routeProvider) { 829 | $routeProvider 830 | .when('/avengers', { 831 | templateUrl: 'avengers.html', 832 | controller: 'AvengersController', 833 | controllerAs: 'avengers', 834 | resolve: { 835 | moviesPrepService: function(movieService) { 836 | return movieService.getMovies(); 837 | } 838 | } 839 | }); 840 | } 841 | 842 | // avengers.controller.js 843 | 844 | class AvengersController { 845 | constructor(moviesPrepService) { 846 | this.movies = moviesPrepService.movies; 847 | } 848 | } 849 | ``` 850 | 851 | Note: The example below shows the route resolve points to a named function, which is easier to debug and easier to handle dependency injection. 852 | 853 | ```javascript 854 | /* even better */ 855 | 856 | // route-config.js 857 | 858 | function config($routeProvider) { 859 | $routeProvider 860 | .when('/avengers', { 861 | templateUrl: 'avengers.html', 862 | controller: 'AvengersController', 863 | controllerAs: 'avengers', 864 | resolve: { 865 | moviesPrepService: moviesPrepService 866 | } 867 | }); 868 | } 869 | 870 | function moviesPrepService(movieService) { 871 | return movieService.getMovies(); 872 | } 873 | 874 | // avengers.controller.js 875 | 876 | class AvengersController { 877 | constructor(moviesPrepService) { 878 | this.movies = moviesPrepService.movies; 879 | } 880 | } 881 | ``` 882 | 883 | **[Back to top](#table-of-contents)** 884 | 885 | ## Components 886 | 887 | A Component module is the container reference for all reusable components. The entities required are decoupled from all other entities and thus can be moved into any other application with ease. As with other entites, keeping the template and controller in separate files reduces component clutter. 888 | 889 | When creating components, a configuration object is supplied as opposed to a function used by directive modules. 890 | 891 | Further, using the `bindings` property is the prefered method as `scope` which is used in directives. All bindings assume isolate scope. 892 | 893 | 894 | ```javascript 895 | /* avoid */ 896 | 897 | const compliment = { 898 | bindings: { 899 | userName: '=', 900 | compliment: '@', 901 | }, 902 | template: '

Hello {{$ctrl.userName}} you look {{$ctrl.compliment}}!

', 903 | controller: function () {/*controller*/} 904 | } 905 | 906 | angular.module('app') 907 | .component('compliment', compliment); 908 | 909 | ``` 910 | 911 | ```javascript 912 | /* recommended */ 913 | 914 | // import template and controller from individual component directory 915 | import controller from './compliment.controller' 916 | import template from './compliment.template.html' 917 | 918 | const compliment = { 919 | bindings: { 920 | userName: '=', 921 | compliment: '@', 922 | }, 923 | template, // template and controller using ES6 shorthand 924 | controller 925 | } 926 | 927 | angular.module('app') 928 | .component('compliment', compliment); 929 | 930 | ``` 931 | 932 | **[Back to top](#table-of-contents)** 933 | 934 | ## Minification and Annotation 935 | 936 | ### ng-annotate 937 | ###### [Style [Y100](#style-y100)] 938 | 939 | - 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'` 940 | 941 | *Why?*: This safeguards your code from any dependencies that may not be using minification-safe practices. 942 | 943 | ```javascript 944 | class Avengers { 945 | constructor(storage, avengerService) { 946 | 'ngInject'; 947 | 948 | this.heroSearch = ''; 949 | 950 | this.avengerService = avengerService; 951 | this.storage = storage; 952 | } 953 | storeHero() { 954 | let hero = this.avengerService.find(this.heroSearch); 955 | this.storage.save(hero.name, hero); 956 | } 957 | } 958 | ``` 959 | 960 | 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. 961 | 962 | ```javascript 963 | // Using @ngInject annotations 964 | function config($routeProvider) { 965 | $routeProvider 966 | .when('/avengers', { 967 | templateUrl: 'avengers.html', 968 | controller: 'AvengersController', 969 | controllerAs: 'avengers', 970 | resolve: { /* @ngInject */ 971 | moviesPrepService: function(movieService) { 972 | return movieService.getMovies(); 973 | } 974 | } 975 | }); 976 | } 977 | ``` 978 | 979 | > 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 minification 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. 980 | `` 981 | 982 | **[Back to top](#table-of-contents)** 983 | 984 | ## Exception Handling 985 | 986 | ### decorators 987 | ###### [Style [Y110](#style-y110)] 988 | 989 | - 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. 990 | 991 | *Why?*: Provides a consistent way to handle uncaught Angular exceptions for development-time or run-time. 992 | 993 | Note: Another option is to override the service instead of using a decorator. This is a fine option, but if you want to keep the default behavior and extend it a decorator is recommended. 994 | 995 | ```javascript 996 | /* recommended */ 997 | 998 | function exceptionConfig($provide) { 999 | 'ngInject'; 1000 | $provide.decorator('$exceptionHandler', extendExceptionHandler); 1001 | } 1002 | 1003 | 1004 | function extendExceptionHandler($delegate, toastr) { 1005 | 'ngInject'; 1006 | return function(exception, cause) { 1007 | $delegate(exception, cause); 1008 | let errorData = { 1009 | exception: exception, 1010 | cause: cause 1011 | }; 1012 | /** 1013 | * Could add the error to a service's collection, 1014 | * add errors to $rootScope, log errors to remote web server, 1015 | * or log locally. Or throw hard. It is entirely up to you. 1016 | * throw exception; 1017 | */ 1018 | toastr.error(exception.msg, errorData); 1019 | }; 1020 | } 1021 | ``` 1022 | 1023 | ### Exception Catchers 1024 | ###### [Style [Y111](#style-y111)] 1025 | 1026 | - Create a factory that exposes an interface to catch and gracefully handle exceptions. 1027 | 1028 | *Why?*: Provides a consistent way to catch exceptions that may be thrown in your code (e.g. during XHR calls or promise failures). 1029 | 1030 | Note: The exception catcher is good for catching and reacting to specific exceptions from calls that you know may throw one. For example, when making an XHR call to retrieve data from a remote web service and you want to catch any exceptions from that service and react uniquely. 1031 | 1032 | ```javascript 1033 | /* recommended */ 1034 | 1035 | class exception { 1036 | constructor(logger) { 1037 | 'ngInject'; 1038 | this.logger = logger; 1039 | } 1040 | catcher(message) { 1041 | return (reason) => { 1042 | this.logger.error(message, reason); 1043 | }; 1044 | } 1045 | } 1046 | ``` 1047 | 1048 | ### Route Errors 1049 | ###### [Style [Y112](#style-y112)] 1050 | 1051 | - Handle and log all routing errors using [`$routeChangeError`](https://docs.angularjs.org/api/ngRoute/service/$route#$routeChangeError). 1052 | 1053 | *Why?*: Provides a consistent way to handle all routing errors. 1054 | 1055 | *Why?*: Potentially provides a better user experience if a routing error occurs and you route them to a friendly screen with more details or recovery options. 1056 | 1057 | ```javascript 1058 | /* recommended */ 1059 | let handlingRouteChangeError = false; 1060 | 1061 | function handleRoutingErrors() { 1062 | /** 1063 | * Route cancellation: 1064 | * On routing error, go to the dashboard. 1065 | * Provide an exit clause if it tries to do it twice. 1066 | */ 1067 | $rootScope.$on('$routeChangeError', 1068 | function(event, current, previous, rejection) { 1069 | if (handlingRouteChangeError) { return; } 1070 | handlingRouteChangeError = true; 1071 | let destination = (current && (current.title || 1072 | current.name || current.loadedTemplateUrl)) || 1073 | 'unknown target'; 1074 | let msg = 'Error routing to ' + destination + '. ' + 1075 | (rejection.msg || ''); 1076 | 1077 | /** 1078 | * Optionally log using a custom service or $log. 1079 | * (Don't forget to inject custom service) 1080 | */ 1081 | logger.warning(msg, [current]); 1082 | 1083 | /** 1084 | * On routing error, go to another route/state. 1085 | */ 1086 | $location.path('/'); 1087 | 1088 | } 1089 | ); 1090 | } 1091 | ``` 1092 | 1093 | **[Back to top](#table-of-contents)** 1094 | 1095 | ## Naming 1096 | 1097 | ### Naming Guidelines 1098 | ###### [Style [Y120](#style-y120)] 1099 | 1100 | - Use consistent names for all entites following a pattern that describes the entities feature then (optionally) its type. My recommended pattern is `feature.type.js`. There are 2 names for most assets: 1101 | * the file name (`avengers.controller.js`) 1102 | * the registered entity name with Angular (`AvengersController`) 1103 | 1104 | *Why?*: Naming conventions help provide a consistent way to find content at a glance. Consistency within the project is vital. Consistency with a team is important. Consistency across a company provides tremendous efficiency. 1105 | 1106 | *Why?*: The naming conventions should simply help you find your code faster and make it easier to understand. 1107 | 1108 | ### Feature File Names 1109 | ###### [Style [Y121](#style-y121)] 1110 | 1111 | - Use consistent names for all entities following a pattern that describes the entity's feature then (optionally) its type. My recommended pattern is `feature.type.js`. 1112 | 1113 | *Why?*: Provides a consistent way to quickly identify individual entities. 1114 | 1115 | *Why?*: Provides pattern matching for any automated tasks. 1116 | 1117 | ```javascript 1118 | /** 1119 | * common options 1120 | */ 1121 | 1122 | // Controllers 1123 | avengers.js 1124 | avengers.controller.js 1125 | avengersController.js 1126 | 1127 | // Services/Factories 1128 | logger.js 1129 | logger.service.js 1130 | loggerService.js 1131 | ``` 1132 | 1133 | ```javascript 1134 | /** 1135 | * recommended 1136 | */ 1137 | 1138 | // controllers 1139 | avengers.controller.js 1140 | avengers.controller.spec.js 1141 | 1142 | // services/factories 1143 | logger.service.js 1144 | logger.service.spec.js 1145 | 1146 | // constants 1147 | constants.js 1148 | 1149 | // module definition 1150 | avengers.module.js 1151 | 1152 | // routes 1153 | avengers.routes.js 1154 | avengers.routes.spec.js 1155 | 1156 | // configuration 1157 | avengers.config.js 1158 | 1159 | // directives 1160 | avenger-profile.directive.js 1161 | avenger-profile.directive.spec.js 1162 | ``` 1163 | 1164 | 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 entities 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`. 1165 | 1166 | ```javascript 1167 | /** 1168 | * recommended 1169 | */ 1170 | // Controllers 1171 | avengers.js 1172 | avengers.spec.js 1173 | ``` 1174 | 1175 | ### Test File Names 1176 | ###### [Style [Y122](#style-y122)] 1177 | 1178 | - Name test specifications similar to the component they test with a suffix of `spec`. 1179 | 1180 | *Why?*: Provides a consistent way to quickly identify components. 1181 | 1182 | *Why?*: Provides pattern matching for [karma](http://karma-runner.github.io/) or other test runners. 1183 | 1184 | ```javascript 1185 | /** 1186 | * recommended 1187 | */ 1188 | avengers.controller.spec.js 1189 | logger.service.spec.js 1190 | avengers.routes.spec.js 1191 | avenger-profile.directive.spec.js 1192 | ``` 1193 | 1194 | ### Controller Names 1195 | ###### [Style [Y123](#style-y123)] 1196 | 1197 | - Use consistent names for all controllers named after their feature. Use UpperCamelCase for controllers, as they are classes. 1198 | 1199 | *Why?*: Provides a consistent way to quickly identify and reference controllers. 1200 | 1201 | *Why?*: UpperCamelCase is conventional for identifying object that can be instantiated using a constructor. 1202 | 1203 | ```javascript 1204 | /** 1205 | * recommended 1206 | */ 1207 | 1208 | // avengers.controller.js 1209 | 1210 | class HeroAvengersController{ 1211 | constructor() { } 1212 | } 1213 | ``` 1214 | 1215 | ### Controller Name Suffix 1216 | ###### [Style [Y124](#style-y124)] 1217 | 1218 | - Append the controller name with the suffix `Controller`. 1219 | 1220 | *Why?*: The `Controller` suffix is more commonly used and is more explicitly descriptive. 1221 | 1222 | ```javascript 1223 | /** 1224 | * recommended 1225 | */ 1226 | 1227 | // avengers.controller.js 1228 | 1229 | class AvengersController{ 1230 | constructor() { } 1231 | } 1232 | ``` 1233 | 1234 | ### Factory and Service Names 1235 | ###### [Style [Y125](#style-y125)] 1236 | 1237 | - Use consistent names for all factories and services named after their feature. Use camel-casing for services and factories. Avoid prefixing factories and services with `$`. Only suffix service and factories with `Service` when it is not clear what they are (i.e. when they are nouns). 1238 | 1239 | *Why?*: Provides a consistent way to quickly identify and reference factories. 1240 | 1241 | *Why?*: Avoids name collisions with built-in factories and services that use the `$` prefix. 1242 | 1243 | *Why?*: Clear service names such as `logger` do not require a suffix. 1244 | 1245 | *Why?*: Service names such as `avengers` are nouns and require a suffix and should be named `avengersService`. 1246 | 1247 | ```javascript 1248 | /** 1249 | * recommended 1250 | */ 1251 | 1252 | // logger.service.js 1253 | 1254 | class logger { 1255 | constructor() { } 1256 | } 1257 | ``` 1258 | 1259 | ```javascript 1260 | /** 1261 | * recommended 1262 | */ 1263 | 1264 | // credit.service.js 1265 | 1266 | class creditService { 1267 | constructor() { } 1268 | } 1269 | 1270 | // customer.service.js 1271 | 1272 | class customersService { 1273 | constructor() { } 1274 | } 1275 | ``` 1276 | 1277 | ### Directive Names 1278 | ###### [Style [Y126](#style-y126)] 1279 | 1280 | - 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). 1281 | 1282 | *Why?*: Provides a consistent way to quickly identify and reference directives. 1283 | 1284 | ```javascript 1285 | /** 1286 | * recommended 1287 | */ 1288 | 1289 | // avenger-profile.directive.js 1290 | 1291 | // usage is 1292 | 1293 | class xxAvengerProfile { 1294 | constructor() { } 1295 | } 1296 | ``` 1297 | 1298 | ### Modules 1299 | ###### [Style [Y127](#style-y127)] 1300 | 1301 | - 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`. 1302 | 1303 | *Why?*: Provides consistency for multiple module apps, and for expanding to large applications. 1304 | 1305 | *Why?*: Provides easy way to use task automation to load all module definitions first, then all other angular files (for bundling). 1306 | 1307 | ### Configuration 1308 | ###### [Style [Y128](#style-y128)] 1309 | 1310 | - 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`. 1311 | 1312 | *Why?*: Separates configuration from module definition, directives, and active code. 1313 | 1314 | *Why?*: Provides an identifiable place to set configuration for a module. 1315 | 1316 | ### Routes 1317 | ###### [Style [Y129](#style-y129)] 1318 | 1319 | - 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. 1320 | 1321 | **[Back to top](#table-of-contents)** 1322 | 1323 | ## Application Structure LIFT Principle 1324 | ### LIFT 1325 | ###### [Style [Y140](#style-y140)] 1326 | 1327 | - 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. 1328 | 1329 | *Why LIFT?*: Provides a consistent structure that scales well, is modular, and makes it easier to increase developer efficiency by finding code quickly. Another way to check your app structure is to ask yourself: How quickly can you open and work in all of the related files for a feature? 1330 | 1331 | When I find my structure is not feeling comfortable, I go back and revisit these LIFT guidelines 1332 | 1333 | 1. `L`ocating our code is easy 1334 | 2. `I`dentify code at a glance 1335 | 3. `F`lat structure as long as we can 1336 | 4. `T`ry to stay DRY (Don’t Repeat Yourself) or T-DRY 1337 | 1338 | ### Locate 1339 | ###### [Style [Y141](#style-y141)] 1340 | 1341 | - Make locating your code intuitive, simple and fast. 1342 | 1343 | *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. 1344 | 1345 | ``` 1346 | /bower_components 1347 | /client 1348 | /app 1349 | /avengers 1350 | /blocks 1351 | /exception 1352 | /logger 1353 | /core 1354 | /dashboard 1355 | /data 1356 | /layout 1357 | /widgets 1358 | /content 1359 | index.html 1360 | .bower.json 1361 | ``` 1362 | 1363 | ### Identify 1364 | ###### [Style [Y142](#style-y142)] 1365 | 1366 | - When you look at a file you should instantly know what it contains and represents. 1367 | 1368 | *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 entity. 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. 1369 | 1370 | ### Flat 1371 | ###### [Style [Y143](#style-y143)] 1372 | 1373 | - Keep a flat folder structure as long as possible. When you get to 7+ files, begin considering separation. 1374 | 1375 | *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. 1376 | 1377 | ### T-DRY (Try to Stick to DRY) 1378 | ###### [Style [Y144](#style-y144)] 1379 | 1380 | - Be DRY, but don't go nuts and sacrifice readability. 1381 | 1382 | *Why?*: Being DRY is important, but not crucial if it sacrifices the others in LIFT, which is why I call it T-DRY. I don’t want to type session-view.html for a view because, well, it’s obviously a view. If it is not obvious or by convention, then I name it. 1383 | 1384 | **[Back to top](#table-of-contents)** 1385 | 1386 | ## Application Structure 1387 | 1388 | ### Overall Guidelines 1389 | ###### [Style [Y150](#style-y150)] 1390 | 1391 | - Have a near term view of implementation and a long term vision. In other words, start small 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`). 1392 | 1393 | Note: Find more details and reasoning behind the structure at [this original post on application structure](http://www.johnpapa.net/angular-app-structuring-guidelines/). 1394 | 1395 | 1396 | ### Folders-by-Feature Structure 1397 | ###### [Style [Y152](#style-y152)] 1398 | 1399 | - 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. 1400 | 1401 | *Why?*: A developer can locate the code, identify what each file represents at a glance, the structure is flat as can be, and there is no repetitive nor redundant names. 1402 | 1403 | *Why?*: The LIFT guidelines are all covered. 1404 | 1405 | *Why?*: Helps reduce the app from becoming cluttered through organizing the content and keeping them aligned with the LIFT guidelines. 1406 | 1407 | *Why?*: When there are a lot of files (10+) locating them is easier with a consistent folder structures and more difficult in flat structures. 1408 | 1409 | ```javascript 1410 | /** 1411 | * recommended 1412 | */ 1413 | 1414 | app/ 1415 | app.module.js 1416 | app.config.js 1417 | components/ 1418 | compliment/ 1419 | compliment.component.js 1420 | compliment.template.html 1421 | compliment.controller.js 1422 | compliment.spec.js 1423 | directives/ 1424 | calendar.directive.js 1425 | calendar.directive.html 1426 | user-profile.directive.js 1427 | user-profile.directive.html 1428 | people/ 1429 | attendees.html 1430 | attendees.controller.js 1431 | people.routes.js 1432 | speakers.html 1433 | speakers.controller.js 1434 | speaker-detail.html 1435 | speaker-detail.controller.js 1436 | services/ 1437 | data.service.js 1438 | localstorage.service.js 1439 | logger.service.js 1440 | spinner.service.js 1441 | sessions/ 1442 | sessions.html 1443 | sessions.controller.js 1444 | sessions.routes.js 1445 | session-detail.html 1446 | session-detail.controller.js 1447 | ``` 1448 | 1449 | ![Sample App Structure](https://raw.githubusercontent.com/rwwagner90/angular-styleguide/master/assets/modularity-2.png) 1450 | 1451 | Note: Do not structure your app 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. 1452 | 1453 | ```javascript 1454 | /* 1455 | * avoid 1456 | * Alternative folders-by-type. 1457 | * I recommend "folders-by-feature", instead. 1458 | */ 1459 | 1460 | app/ 1461 | app.module.js 1462 | app.config.js 1463 | app.routes.js 1464 | directives.js 1465 | controllers/ 1466 | attendees.js 1467 | session-detail.js 1468 | sessions.js 1469 | shell.js 1470 | speakers.js 1471 | speaker-detail.js 1472 | topnav.js 1473 | directives/ 1474 | calendar.directive.js 1475 | calendar.directive.html 1476 | user-profile.directive.js 1477 | user-profile.directive.html 1478 | services/ 1479 | dataservice.js 1480 | localstorage.js 1481 | logger.js 1482 | spinner.js 1483 | views/ 1484 | attendees.html 1485 | session-detail.html 1486 | sessions.html 1487 | shell.html 1488 | speakers.html 1489 | speaker-detail.html 1490 | topnav.html 1491 | ``` 1492 | 1493 | **[Back to top](#table-of-contents)** 1494 | 1495 | ## Modularity 1496 | 1497 | ### Many Small, Self Contained Modules 1498 | ###### [Style [Y160](#style-y160)] 1499 | 1500 | - Create small modules that encapsulate one responsibility. 1501 | 1502 | *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. 1503 | 1504 | ### Create an App Module 1505 | ###### [Style [Y161](#style-y161)] 1506 | 1507 | - Create an application root module whose role is pull together all of the modules and features of your application. Name this for your application. 1508 | 1509 | *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. 1510 | 1511 | ### Keep the App Module Thin 1512 | ###### [Style [Y162](#style-y162)] 1513 | 1514 | - Only put logic for pulling together the app in the application module. Leave features in their own modules. 1515 | 1516 | *Why?*: Adding additional roles to the application root to get remote data, display views, or other logic not related to pulling the app together muddies the app module and make both sets of features harder to reuse or turn off. 1517 | 1518 | *Why?*: The app module becomes a manifest that describes which modules help define the application. 1519 | 1520 | ### Feature Areas are Modules 1521 | ###### [Style [Y163](#style-y163)] 1522 | 1523 | - Create modules that represent feature areas, such as layout, reusable and shared services, dashboards, and app specific features (e.g. customers, admin, sales). 1524 | 1525 | *Why?*: Self contained modules can be added to the application with little or no friction. 1526 | 1527 | *Why?*: Sprints or iterations can focus on feature areas and turn them on at the end of the sprint or iteration. 1528 | 1529 | *Why?*: Separating feature areas into modules makes it easier to test the modules in isolation and reuse code. 1530 | 1531 | ### Reusable Blocks are Modules 1532 | ###### [Style [Y164](#style-y164)] 1533 | 1534 | - Create modules that represent reusable application blocks for common services such as exception handling, logging, diagnostics, security, and local data stashing. 1535 | 1536 | *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. 1537 | 1538 | ### Module Dependencies 1539 | ###### [Style [Y165](#style-y165)] 1540 | 1541 | - The application root module depends on the app specific feature modules and any shared or reusable modules. 1542 | 1543 | ![Modularity and Dependencies](https://raw.githubusercontent.com/rwwagner90/angular-styleguide/master/assets/modularity-1.png) 1544 | 1545 | *Why?*: The main app module contains a quickly identifiable manifest of the application's features. 1546 | 1547 | *Why?*: Each feature area contains a manifest of what it depends on, so it can be pulled in as a dependency in other applications and still work. 1548 | 1549 | *Why?*: Intra-App features such as shared data services become easy to locate and share from within `app.core` (choose your favorite name for this module). 1550 | 1551 | Note: This is a strategy for consistency. There are many good options here. Choose one that is consistent, follows Angular's dependency rules, and is easy to maintain and scale. 1552 | 1553 | > My structures vary slightly between projects but they all follow these guidelines for structure and modularity. The implementation may vary depending on the features and the team. In other words, don't get hung up on an exact like-for-like structure but do justify your structure using consistency, maintainability, and efficiency in mind. 1554 | 1555 | > In a small app, you can also consider putting all the shared dependencies in the app module where the feature modules have no direct dependencies. This makes it easier to maintain the smaller application, but makes it harder to reuse modules outside of this application. 1556 | 1557 | **[Back to top](#table-of-contents)** 1558 | 1559 | ## Startup Logic 1560 | 1561 | ### Run Blocks 1562 | ###### [Style [Y171](#style-y171)] 1563 | 1564 | - 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). 1565 | 1566 | *Why?*: Code directly in a run block can be difficult to test. Placing in a factory makes it easier to abstract and mock. 1567 | 1568 | ```javascript 1569 | angular 1570 | .module('app') 1571 | .run(runBlock); 1572 | 1573 | function runBlock(authenticator, translator) { 1574 | 'ngInject'; 1575 | authenticator.initialize(); 1576 | translator.initialize(); 1577 | } 1578 | ``` 1579 | 1580 | **[Back to top](#table-of-contents)** 1581 | 1582 | ## Angular $ Wrapper Services 1583 | 1584 | ### $document and $window 1585 | ###### [Style [Y180](#style-y180)] 1586 | 1587 | - 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`. 1588 | 1589 | *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. 1590 | 1591 | ### $timeout and $interval 1592 | ###### [Style [Y181](#style-y181)] 1593 | 1594 | - 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` . 1595 | 1596 | *Why?*: These services are wrapped by Angular and more easily testable and handle Angular's digest cycle thus keeping data binding in sync. 1597 | 1598 | **[Back to top](#table-of-contents)** 1599 | 1600 | ## Testing 1601 | Unit testing helps maintain clean code, as such I included some of my recommendations for unit testing foundations with links for more information. 1602 | 1603 | ### Write Tests with Stories 1604 | ###### [Style [Y190](#style-y190)] 1605 | 1606 | - 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. 1607 | 1608 | *Why?*: Writing the test descriptions helps clearly define what your story will do, will not do, and how you can measure success. 1609 | 1610 | ```javascript 1611 | it('should have Avengers controller', function() { 1612 | // TODO 1613 | }); 1614 | 1615 | it('should find 1 Avenger when filtered by name', function() { 1616 | // TODO 1617 | }); 1618 | 1619 | it('should have 10 Avengers', function() { 1620 | // TODO (mock data?) 1621 | }); 1622 | 1623 | it('should return Avengers via XHR', function() { 1624 | // TODO ($httpBackend?) 1625 | }); 1626 | 1627 | // and so on 1628 | ``` 1629 | 1630 | ### Testing Library 1631 | ###### [Style [Y191](#style-y191)] 1632 | 1633 | - Use [Jasmine](http://jasmine.github.io/) or [Mocha](http://mochajs.org) for unit testing. 1634 | 1635 | *Why?*: Both Jasmine and Mocha are widely used in the Angular community. Both are stable, well maintained, and provide robust testing features. 1636 | 1637 | Note: When using Mocha, also consider choosing an assert library such as [Chai](http://chaijs.com). I prefer Mocha. 1638 | 1639 | ### Test Runner 1640 | ###### [Style [Y192](#style-y192)] 1641 | 1642 | - Use [Karma](http://karma-runner.github.io) as a test runner. 1643 | 1644 | *Why?*: Karma is easy to configure to run once or automatically when you change your code. 1645 | 1646 | *Why?*: Karma hooks into your Continuous Integration process easily on its own or through Grunt or Gulp. 1647 | 1648 | *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). 1649 | 1650 | *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). When using Gulp, use [Karma](https://github.com/karma-runner/karma) directly and not with a plugin as the API can be called directly. 1651 | 1652 | ```javascript 1653 | /* recommended */ 1654 | 1655 | // Gulp example with Karma directly 1656 | function startTests(singleRun, done) { 1657 | var child; 1658 | var excludeFiles = []; 1659 | var fork = require('child_process').fork; 1660 | var karma = require('karma').server; 1661 | var serverSpecs = config.serverIntegrationSpecs; 1662 | 1663 | if (args.startServers) { 1664 | log('Starting servers'); 1665 | var savedEnv = process.env; 1666 | savedEnv.NODE_ENV = 'dev'; 1667 | savedEnv.PORT = 8888; 1668 | child = fork(config.nodeServer); 1669 | } else { 1670 | if (serverSpecs && serverSpecs.length) { 1671 | excludeFiles = serverSpecs; 1672 | } 1673 | } 1674 | 1675 | karma.start({ 1676 | configFile: __dirname + '/karma.conf.js', 1677 | exclude: excludeFiles, 1678 | singleRun: !!singleRun 1679 | }, karmaCompleted); 1680 | 1681 | //////////////// 1682 | 1683 | function karmaCompleted(karmaResult) { 1684 | log('Karma completed'); 1685 | if (child) { 1686 | log('shutting down the child process'); 1687 | child.kill(); 1688 | } 1689 | if (karmaResult === 1) { 1690 | done('karma: tests failed with code ' + karmaResult); 1691 | } else { 1692 | done(); 1693 | } 1694 | } 1695 | } 1696 | ``` 1697 | 1698 | ### Stubbing and Spying 1699 | ###### [Style [Y193](#style-y193)] 1700 | 1701 | - Use [Sinon](http://sinonjs.org/) for stubbing and spying. 1702 | 1703 | *Why?*: Sinon works well with both Jasmine and Mocha and extends the stubbing and spying features they offer. 1704 | 1705 | *Why?*: Sinon makes it easier to toggle between Jasmine and Mocha, if you want to try both. 1706 | 1707 | *Why?*: Sinon has descriptive messages when tests fail the assertions. 1708 | 1709 | ### Headless Browser 1710 | ###### [Style [Y194](#style-y194)] 1711 | 1712 | - Use [PhantomJS](http://phantomjs.org/) to run your tests on a server. 1713 | 1714 | *Why?*: PhantomJS is a headless browser that helps run your tests without needing a "visual" browser. So you do not have to install Chrome, Safari, IE, or other browsers on your server. 1715 | 1716 | Note: You should still test on all browsers in your environment, as appropriate for your target audience. 1717 | 1718 | ### Code Analysis 1719 | ###### [Style [Y195](#style-y195)] 1720 | 1721 | - Run ESLint on your tests. 1722 | 1723 | *Why?*: Tests are code. ESLint can help identify code quality issues that may cause the test to work improperly. 1724 | 1725 | ### Organizing Tests 1726 | ###### [Style [Y197](#style-y197)] 1727 | 1728 | - 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. 1729 | 1730 | *Why?*: Unit tests have a direct correlation to a specific component and file in source code. 1731 | 1732 | *Why?*: It is easier to keep them up to date since they are always in sight. When coding whether you do TDD or test during development or test after development, the specs are side-by-side and never out of sight nor mind, and thus more likely to be maintained which also helps maintain code coverage. 1733 | 1734 | *Why?*: When you update source code it is easier to go update the tests at the same time. 1735 | 1736 | *Why?*: Placing them side-by-side makes it easy to find them and easy to move them with the source code if you move the source. 1737 | 1738 | *Why?*: Having the spec nearby makes it easier for the source code reader to learn how the component is supposed to be used and to discover its known limitations. 1739 | 1740 | *Why?*: Separating specs so they are not in a distributed build is easy with grunt or gulp. 1741 | 1742 | ``` 1743 | /src/client/app/customers/customer-detail.controller.js 1744 | /customer-detail.controller.spec.js 1745 | /customers.controller.js 1746 | /customers.controller.spec.js 1747 | /customers.module.js 1748 | /customers.route.js 1749 | /customers.route.spec.js 1750 | ``` 1751 | 1752 | **[Back to top](#table-of-contents)** 1753 | 1754 | ## Animations 1755 | 1756 | ### Usage 1757 | ###### [Style [Y210](#style-y210)] 1758 | 1759 | - 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. 1760 | 1761 | *Why?*: Subtle animations can improve User Experience when used appropriately. 1762 | 1763 | *Why?*: Subtle animations can improve perceived performance as views transition. 1764 | 1765 | ### Sub Second 1766 | ###### [Style [Y211](#style-y211)] 1767 | 1768 | - Use short durations for animations. I generally start with 300ms and adjust until appropriate. 1769 | 1770 | *Why?*: Long animations can have the reverse affect on User Experience and perceived performance by giving the appearance of a slow application. 1771 | 1772 | ### animate.css 1773 | ###### [Style [Y212](#style-y212)] 1774 | 1775 | - Use [animate.css](http://daneden.github.io/animate.css/) for conventional animations. 1776 | 1777 | *Why?*: The animations that animate.css provides are fast, smooth, and easy to add to your application. 1778 | 1779 | *Why?*: Provides consistency in your animations. 1780 | 1781 | *Why?*: animate.css is widely used and tested. 1782 | 1783 | Note: See this [great post by Matias Niemelä on Angular animations](http://www.yearofmoo.com/2013/08/remastered-animation-in-angularjs-1-2.html) 1784 | 1785 | **[Back to top](#table-of-contents)** 1786 | 1787 | ## Comments 1788 | 1789 | ### jsDoc 1790 | ###### [Style [Y220](#style-y220)] 1791 | 1792 | - 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. 1793 | 1794 | *Why?*: You can generate (and regenerate) documentation from your code, instead of writing it from scratch. 1795 | 1796 | *Why?*: Provides consistency using a common industry tool. 1797 | 1798 | ```javascript 1799 | /** 1800 | * Logger Factory 1801 | * @namespace Factories 1802 | */ 1803 | (function() { 1804 | angular 1805 | .module('app') 1806 | .factory('logger', logger); 1807 | 1808 | /** 1809 | * @namespace Logger 1810 | * @desc Application wide logger 1811 | * @memberOf Factories 1812 | */ 1813 | function logger($log) { 1814 | var service = { 1815 | logError: logError 1816 | }; 1817 | return service; 1818 | 1819 | //////////// 1820 | 1821 | /** 1822 | * @name logError 1823 | * @desc Logs errors 1824 | * @param {String} msg Message to log 1825 | * @returns {String} 1826 | * @memberOf Factories.Logger 1827 | */ 1828 | function logError(msg) { 1829 | var loggedMsg = 'Error: ' + msg; 1830 | $log.error(loggedMsg); 1831 | return loggedMsg; 1832 | }; 1833 | } 1834 | })(); 1835 | ``` 1836 | 1837 | **[Back to top](#table-of-contents)** 1838 | 1839 | ## ESLint 1840 | 1841 | ### Use an Options File 1842 | ###### [Style [Y230](#style-y230)] 1843 | 1844 | - Use ESLint for linting your JavaScript and be sure to customize the .eslintrc file and include in source control. See the [ESLint docs](http://eslint.org/docs/rules/) for details on the options. 1845 | 1846 | *Why?*: Provides a first alert prior to committing any code to source control. 1847 | 1848 | *Why?*: Provides consistency across your team. 1849 | 1850 | *Why?*: ESLint supports ES6. 1851 | 1852 | ```javascript 1853 | { 1854 | "globals": { 1855 | "_": false, 1856 | "angular": false, 1857 | "console": false, 1858 | "inject": false, 1859 | "module": false, 1860 | "window": false 1861 | }, 1862 | "parser": "babel-eslint", 1863 | "rules": { 1864 | "brace-style": [2, "stroustrup", {"allowSingleLine": false}], 1865 | "camelcase": 1, 1866 | "comma-dangle": [1, "never"], 1867 | "curly": 1, 1868 | "dot-notation": 1, 1869 | "eqeqeq": 1, 1870 | "indent": [1, 2], 1871 | "lines-around-comment": [2, {"allowBlockStart": true, "beforeBlockComment": true, "beforeLineComment": true}], 1872 | "new-parens": 1, 1873 | "no-bitwise": 1, 1874 | "no-cond-assign": 1, 1875 | "no-debugger": 1, 1876 | "no-dupe-args": 1, 1877 | "no-dupe-keys": 1, 1878 | "no-empty": 1, 1879 | "no-invalid-regexp": 1, 1880 | "no-invalid-this": 1, 1881 | "no-mixed-spaces-and-tabs": [1, "smart-tabs"], 1882 | "no-multiple-empty-lines": [1, {"max": 2}], 1883 | "no-undef": 1, 1884 | "no-underscore-dangle": 1, 1885 | "no-unreachable": 1, 1886 | "no-unused-vars": 1, 1887 | "one-var": [1, "never"], 1888 | "quote-props": [1, "as-needed"], 1889 | "semi": [1, "always"], 1890 | "keyword-spacing": 1, 1891 | "space-unary-ops": [1, {"words": true, "nonwords": false}], 1892 | "strict": [1, "function"], 1893 | "vars-on-top": 1, 1894 | "wrap-iife": [1, "outside"], 1895 | "yoda": [1, "never"], 1896 | 1897 | //ES6 Stuff 1898 | "arrow-parens": 1, 1899 | "arrow-spacing": 1, 1900 | "constructor-super": 1, 1901 | "no-class-assign": 1, 1902 | "no-const-assign": 1, 1903 | "no-dupe-class-members": 1, 1904 | "no-this-before-super": 1, 1905 | "no-var": 1, 1906 | "object-shorthand": 1, 1907 | "prefer-arrow-callback": 1, 1908 | "prefer-const": 1 1909 | } 1910 | } 1911 | 1912 | ``` 1913 | **[Back to top](#table-of-contents)** 1914 | 1915 | ## Constants 1916 | 1917 | ### Vendor Globals 1918 | ###### [Style [Y240](#style-y240)] 1919 | 1920 | - Create an Angular Constant for vendor libraries' global variables. 1921 | 1922 | *Why?*: Provides a way to inject vendor libraries that otherwise are globals. This improves code testability by allowing you to more easily know what the dependencies of your constants are (avoids leaky abstractions). It also allows you to mock these dependencies, where it makes sense. 1923 | 1924 | ```javascript 1925 | // constants.js 1926 | 1927 | /* global toastr:false, moment:false */ 1928 | (function() { 1929 | 'use strict'; 1930 | 1931 | angular 1932 | .module('app.core') 1933 | .constant('toastr', toastr) 1934 | .constant('moment', moment); 1935 | })(); 1936 | ``` 1937 | 1938 | ###### [Style [Y241](#style-y241)] 1939 | 1940 | - 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. 1941 | 1942 | *Why?*: A value that may change, even infrequently, should be retrieved from a service so you do not have to change the source code. For example, a url for a data service could be placed in a constants but a better place would be to load it from a web service. 1943 | 1944 | *Why?*: Constants can be injected into any angular component, including providers. 1945 | 1946 | *Why?*: When an application is separated into modules that may be reused in other applications, each stand-alone module should be able to operate on its own including any dependent constants. 1947 | 1948 | ```javascript 1949 | // Constants used by the entire app 1950 | angular 1951 | .module('app.core') 1952 | .constant('moment', moment); 1953 | 1954 | // Constants used only by the sales module 1955 | angular 1956 | .module('app.sales') 1957 | .constant('events', { 1958 | ORDER_CREATED: 'event_order_created', 1959 | INVENTORY_DEPLETED: 'event_inventory_depleted' 1960 | }); 1961 | ``` 1962 | 1963 | **[Back to top](#table-of-contents)** 1964 | 1965 | 1966 | ## Yeoman Generator 1967 | ###### [Style [Y260](#style-y260)] 1968 | 1969 | You can use the [generator-gulp-angular](https://github.com/Swiip/generator-gulp-angular) to create an app that serves as a starting point for Angular that follows this style guide. 1970 | 1971 | 1. Install generator-gulp-angular 1972 | 1973 | ``` 1974 | npm install -g generator-gulp-angular 1975 | ``` 1976 | 1977 | 2. Create a new folder and change directory to it 1978 | 1979 | ``` 1980 | mkdir myapp 1981 | cd myapp 1982 | ``` 1983 | 1984 | 3. Run the generator 1985 | 1986 | ``` 1987 | yo gulp-angular 1988 | ``` 1989 | 1990 | **[Back to top](#table-of-contents)** 1991 | 1992 | ## Routing 1993 | Client-side routing is important for creating a navigation flow between views and composing views that are made of many smaller templates and directives. 1994 | 1995 | ###### [Style [Y270](#style-y270)] 1996 | 1997 | - Use the [AngularUI Router](http://angular-ui.github.io/ui-router/) for client-side routing. 1998 | 1999 | *Why?*: UI Router offers all the features of the Angular router plus a few additional ones including nested routes and states. 2000 | 2001 | *Why?*: The syntax is quite similar to the Angular router and is easy to migrate to UI Router. 2002 | 2003 | - Note: You can use a provider such as the `routerHelperProvider` shown below to help configure states across files, during the run phase. 2004 | 2005 | ```javascript 2006 | // customers.routes.js 2007 | angular 2008 | .module('app.customers') 2009 | .run(appRun); 2010 | 2011 | 2012 | function appRun(routerHelper) { 2013 | 'ngInject'; 2014 | routerHelper.configureStates(getStates()); 2015 | } 2016 | 2017 | function getStates() { 2018 | return [ 2019 | { 2020 | state: 'customer', 2021 | config: { 2022 | abstract: true, 2023 | template: '', 2024 | url: '/customer' 2025 | } 2026 | } 2027 | ]; 2028 | } 2029 | ``` 2030 | 2031 | ```javascript 2032 | // routerHelperProvider.js 2033 | angular 2034 | .module('blocks.router') 2035 | .provider('routerHelper', routerHelperProvider); 2036 | 2037 | 2038 | function routerHelperProvider($locationProvider, $stateProvider, $urlRouterProvider) { 2039 | 'ngInject'; 2040 | /* jshint validthis:true */ 2041 | this.$get = RouterHelper; 2042 | 2043 | $locationProvider.html5Mode(true); 2044 | function RouterHelper($state) { 2045 | 'ngInject'; 2046 | var hasOtherwise = false; 2047 | var service = { 2048 | configureStates: configureStates, 2049 | getStates: getStates 2050 | }; 2051 | 2052 | return service; 2053 | 2054 | /////////////// 2055 | 2056 | function configureStates(states, otherwisePath) { 2057 | states.forEach((state) => { 2058 | $stateProvider.state(state.state, state.config); 2059 | }); 2060 | if (otherwisePath && !hasOtherwise) { 2061 | hasOtherwise = true; 2062 | $urlRouterProvider.otherwise(otherwisePath); 2063 | } 2064 | } 2065 | 2066 | function getStates() { return $state.get(); } 2067 | } 2068 | } 2069 | ``` 2070 | 2071 | ###### [Style [Y271](#style-y271)] 2072 | 2073 | - Define routes for views in the module where they exist. Each module should contain the routes for the views in the module. 2074 | 2075 | *Why?*: Each module should be able to stand on its own. 2076 | 2077 | *Why?*: When removing a module or adding a module, the app will only contain routes that point to existing views. 2078 | 2079 | *Why?*: This makes it easy to enable or disable portions of an application without concern over orphaned routes. 2080 | 2081 | **[Back to top](#table-of-contents)** 2082 | 2083 | ## Task Automation 2084 | 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. 2085 | 2086 | > Learn more about gulp and patterns for task automation in my [Gulp Pluralsight course](http://jpapa.me/gulpps) 2087 | 2088 | ###### [Style [Y400](#style-y400)] 2089 | 2090 | - Use task automation to list module definition files `*.module.js` before all other application JavaScript files. 2091 | 2092 | *Why?*: Angular needs the module definitions to be registered before they are used. 2093 | 2094 | *Why?*: Naming modules with a specific pattern such as `*.module.js` makes it easy to grab them with a glob and list them first. 2095 | 2096 | ```javascript 2097 | var clientApp = './src/client/app/'; 2098 | 2099 | // Always grab module files first 2100 | var files = [ 2101 | clientApp + '**/*.module.js', 2102 | clientApp + '**/*.js' 2103 | ]; 2104 | ``` 2105 | 2106 | **[Back to top](#table-of-contents)** 2107 | 2108 | ## Filters 2109 | 2110 | ###### [Style [Y420](#style-y420)] 2111 | 2112 | - Avoid using filters for scanning all properties of a complex object graph. Use filters for select properties. 2113 | 2114 | *Why?*: Filters can easily be abused and negatively affect performance if not used wisely, for example when a filter hits a large and deep object graph. 2115 | 2116 | **[Back to top](#table-of-contents)** 2117 | 2118 | ## Angular docs 2119 | For anything else, API reference, check the [Angular documentation](//docs.angularjs.org/api). 2120 | 2121 | ## Contributing 2122 | 2123 | Open an issue first to discuss potential changes/additions. If you have questions with the guide, feel free to leave them as issues in the repository. If you find a typo, create a pull request. The idea is to keep the content up to date and use github’s native feature to help tell the story with issues and PR’s, which are all searchable via google. Why? Because odds are if you have a question, someone else does too! You can learn more here at about how to contribute. 2124 | 2125 | *By contributing to this repository you are agreeing to make your content available subject to the license of this repository.* 2126 | 2127 | ### Process 2128 | 1. Discuss the changes in a GitHub issue. 2129 | 2. Open a Pull Request, reference the issue, and explain the change and why it adds value. 2130 | 3. The Pull Request will be evaluated and either merged or declined. 2131 | 2132 | ## License 2133 | 2134 | _tldr; Use this guide. Attributions are appreciated._ 2135 | 2136 | ### Copyright 2137 | 2138 | Copyright (c) 2014-2016 [John Papa](http://johnpapa.net) 2139 | and 2015 Robert Wagner / Mike Erickson 2140 | 2141 | ### (The MIT License) 2142 | Permission is hereby granted, free of charge, to any person obtaining 2143 | a copy of this software and associated documentation files (the 2144 | 'Software'), to deal in the Software without restriction, including 2145 | without limitation the rights to use, copy, modify, merge, publish, 2146 | distribute, sublicense, and/or sell copies of the Software, and to 2147 | permit persons to whom the Software is furnished to do so, subject to 2148 | the following conditions: 2149 | 2150 | The above copyright notice and this permission notice shall be 2151 | included in all copies or substantial portions of the Software. 2152 | 2153 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 2154 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 2155 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 2156 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 2157 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 2158 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 2159 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 2160 | 2161 | **[Back to top](#table-of-contents)** 2162 | -------------------------------------------------------------------------------- /README.original.md: -------------------------------------------------------------------------------- 1 | # Angular Style Guide ES2015/ES6 2 | 3 | This is an ES2015/ES6 fork of the popular Angular 1.x Style Guide by John Papa. It was originally written for use with generator-gulp-angular and Babel, and things that do not apply in that circumstance have been removed. 4 | 5 | **Note:** generator-gulp-angular has been deprecated, but the people working on it are now working on [FountainJS](http://fountainjs.io/), which I highly recommend as a starting point. Just make sure to choose Angular 1.x and Babel to stay in line with this guide. 6 | 7 | Please be advised that all examples will not be copy/paste working examples. In instances where classes are imported, 8 | it is expected that the imported class was defined correctly, in another file, and imported in. 9 | 10 | ## Table of Contents 11 | 12 | 1. [Single Responsibility](#single-responsibility) 13 | 1. [Modules](#modules) 14 | 1. [Controllers](#controllers) 15 | 1. [Factories](#factories) 16 | 1. [Data Services](#data-services) 17 | 1. [Directives](#directives) 18 | 1. [Components](#components) 19 | 1. [Resolving Promises](#route-resolve-promises) 20 | 1. [Minification and Annotation](#minification-and-annotation) 21 | 1. [Exception Handling](#exception-handling) 22 | 1. [Naming](#naming) 23 | 1. [Application Structure LIFT Principle](#application-structure-lift-principle) 24 | 1. [Application Structure](#application-structure) 25 | 1. [Modularity](#modularity) 26 | 1. [Startup Logic](#startup-logic) 27 | 1. [Angular $ Wrapper Services](#angular--wrapper-services) 28 | 1. [Testing](#testing) 29 | 1. [Animations](#animations) 30 | 1. [Comments](#comments) 31 | 1. [ESLint](#eslint) 32 | 1. [Constants](#constants) 33 | 1. [File Templates and Snippets](#file-templates-and-snippets) 34 | 1. [Yeoman Generator](#yeoman-generator) 35 | 1. [Routing](#routing) 36 | 1. [Task Automation](#task-automation) 37 | 1. [Filters](#filters) 38 | 1. [Angular Docs](#angular-docs) 39 | 1. [Contributing](#contributing) 40 | 1. [License](#license) 41 | 42 | ## Single Responsibility 43 | 44 | ### Rule of 1 45 | ###### [Style [Y001](#style-y001)] 46 | 47 | - Define 1 module per file. 48 | 49 | The following example defines the `app` module and its dependencies, defines a controller, and defines a factory all in the same file. 50 | 51 | ```js 52 | /* avoid */ 53 | 54 | class SomeController { 55 | constructor() { } 56 | } 57 | 58 | class someFactory{ 59 | constructor() { } 60 | } 61 | 62 | angular 63 | .module('app', ['ngRoute']) 64 | .controller('SomeController', SomeController) 65 | .factory('someFactory', someFactory); 66 | ``` 67 | 68 | The same parts are now separated into their own files. 69 | 70 | ```js 71 | /* recommended */ 72 | 73 | // app.module.js 74 | import { SomeController } from './some.controller'; 75 | import { someFactory } from './some.factory'; 76 | 77 | angular 78 | .module('app', ['ngRoute']) 79 | .controller('SomeController', SomeController) 80 | .factory('someFactory', someFactory); 81 | ``` 82 | 83 | ```javascript 84 | /* recommended */ 85 | 86 | // some.controller.js 87 | class SomeController{ 88 | constructor() { } 89 | } 90 | ``` 91 | 92 | ```javascript 93 | /* recommended */ 94 | 95 | // some.factory.js 96 | class someFactory{ 97 | constructor() { } 98 | } 99 | ``` 100 | 101 | **[Back to top](#table-of-contents)** 102 | 103 | ## Modules 104 | 105 | ### Avoid Naming Collisions 106 | ###### [Style [Y020](#style-y020)] 107 | 108 | - Use unique naming conventions with separators for sub-modules. 109 | 110 | *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`. 111 | 112 | ### Definitions (aka Setters) 113 | ###### [Style [Y021](#style-y021)] 114 | 115 | - Declare modules without a variable using the setter syntax. 116 | 117 | *Why?*: With 1 part per file, there is rarely a need to introduce a variable for the module. 118 | 119 | ```javascript 120 | /* avoid */ 121 | const app = angular.module('app', [ 122 | 'ngAnimate', 123 | 'ngRoute', 124 | 'app.shared', 125 | 'app.dashboard' 126 | ]); 127 | ``` 128 | 129 | Instead use the simple setter syntax. 130 | 131 | ```javascript 132 | /* recommended */ 133 | angular 134 | .module('app', [ 135 | 'ngAnimate', 136 | 'ngRoute', 137 | 'app.shared', 138 | 'app.dashboard' 139 | ]); 140 | ``` 141 | 142 | ### Getters 143 | ###### [Style [Y022](#style-y022)] 144 | 145 | - When using a module, avoid unnecessarily using variables and instead use chaining with the getter syntax. 146 | 147 | *Why?*: This produces more readable code and avoids variable collisions or leaks. 148 | 149 | ```javascript 150 | /* avoid */ 151 | import { SomeController } from './some.controller'; 152 | 153 | const app = angular.module('app'); 154 | app.controller('SomeController', SomeController); 155 | ``` 156 | 157 | ```javascript 158 | /* recommended */ 159 | import { SomeController } from './some.controller'; 160 | 161 | angular 162 | .module('app') 163 | .controller('SomeController', SomeController); 164 | ``` 165 | 166 | ### Setting vs Getting 167 | ###### [Style [Y023](#style-y023)] 168 | 169 | - Only set once and get for all other instances. 170 | 171 | *Why?*: A module should only be created once, then retrieved from that point and after. 172 | 173 | ```javascript 174 | /* recommended */ 175 | 176 | // to set a module 177 | angular.module('app', []); 178 | 179 | // to get a module 180 | angular.module('app'); 181 | ``` 182 | 183 | ### Named vs Anonymous Functions 184 | ###### [Style [Y024](#style-y024)] 185 | 186 | - Use named functions instead of passing an anonymous function in as a callback. 187 | 188 | *Why?*: This produces more readable code, is much easier to debug, and reduces the amount of nested callback code. 189 | 190 | ```javascript 191 | /* avoid */ 192 | angular 193 | .module('app') 194 | .controller('Dashboard', () => { }) 195 | ``` 196 | 197 | ```javascript 198 | /* recommended */ 199 | 200 | // dashboard.controller.js 201 | 202 | class Dashboard { 203 | constructor() { } 204 | } 205 | 206 | // index.module.js 207 | import { Dashboard } from './dashboard.controller'; 208 | 209 | angular 210 | .module('app') 211 | .controller('Dashboard', Dashboard); 212 | ``` 213 | 214 | **[Back to top](#table-of-contents)** 215 | 216 | ## Controllers 217 | 218 | ### controllerAs View Syntax 219 | ###### [Style [Y030](#style-y030)] 220 | 221 | - 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. 222 | 223 | *Why?*: Controllers are constructed, "newed" up, and provide a single new instance, and the `controllerAs` syntax is closer to that of a JavaScript constructor than the `classic $scope syntax`. 224 | 225 | *Why?*: It promotes the use of binding to a "dotted" object in the View (e.g. `customer.name` instead of `name`), which is more contextual, easier to read, and avoids any reference issues that may occur without "dotting". 226 | 227 | *Why?*: Helps avoid using `$parent` calls in Views with nested controllers. 228 | 229 | ```html 230 | 231 |
232 | {{name}} 233 |
234 | ``` 235 | 236 | ```html 237 | 238 |
239 | {{customer.name}} 240 |
241 | ``` 242 | 243 | controllerAs can also be used in the router like so: 244 | 245 | ```js 246 | .when('/dropbox', { 247 | templateUrl: 'views/dropbox.html', 248 | controller: 'DropboxCtrl', 249 | controllerAs: 'dropbox' 250 | }) 251 | ``` 252 | 253 | ### controllerAs Controller Syntax 254 | ###### [Style [Y031](#style-y031)] 255 | 256 | - Use the `controllerAs` syntax over the `classic controller with $scope` syntax. 257 | 258 | - The `controllerAs` syntax uses `this` inside controllers which gets bound to `$scope` 259 | 260 | *Why?*: `controllerAs` is syntactic sugar over `$scope`. You can still bind to the View and still access `$scope` methods. 261 | 262 | *Why?*: Helps avoid the temptation of using `$scope` methods inside a controller when it may otherwise be better to avoid them or move the method to a factory, and reference them from the controller. Consider using `$scope` in a controller only when needed. For example when publishing and subscribing events using [`$emit`](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$emit), [`$broadcast`](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$broadcast), or [`$on`](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$on) consider moving these uses to a factory and invoke from the controller. 263 | 264 | ```javascript 265 | /* avoid */ 266 | class Customer { 267 | constructor($scope) { 268 | $scope.name = {}; 269 | $scope.sendMessage = function() { }; 270 | } 271 | } 272 | ``` 273 | 274 | ```javascript 275 | /* recommended - but see next section */ 276 | class Customer { 277 | constructor() { 278 | this.name = {}; 279 | } 280 | sendMessage(){ } 281 | } 282 | ``` 283 | 284 | ### controllerAs with fat arrows 285 | ###### [Style [Y032](#style-y032)] 286 | 287 | - Using a capture variable for `this` when using the `controllerAs` syntax, is not necessary with ES6. You can simply use a fat arrow to automatically reference the correct `this` context 288 | 289 | 290 | ```javascript 291 | /* avoid */ 292 | let self = this; 293 | () => { 294 | self.foo = 'bar'; 295 | } 296 | ``` 297 | 298 | ```javascript 299 | /* recommended */ 300 | () => { 301 | this.foo = 'bar'; 302 | } 303 | ``` 304 | 305 | ### Defer Controller Logic to Services 306 | ###### [Style [Y035](#style-y035)] 307 | 308 | - Defer logic in a controller by delegating to services and factories. 309 | 310 | *Why?*: Logic may be reused by multiple controllers when placed within a service and exposed via a function. 311 | 312 | *Why?*: Logic in a service can more easily be isolated in a unit test, while the calling logic in the controller can be easily mocked. 313 | 314 | *Why?*: Removes dependencies and hides implementation details from the controller. 315 | 316 | *Why?*: Keeps the controller slim, trim, and focused. 317 | 318 | ```javascript 319 | 320 | /* avoid */ 321 | class Order { 322 | constructor($http, $q, config, userInfo) { 323 | this.isCreditOk = false; 324 | this.total = 0; 325 | } 326 | checkCredit() { 327 | let settings = {}; 328 | // Get the credit service base URL from config 329 | // Set credit service required headers 330 | // Prepare URL query string or data object with request data 331 | // Add user-identifying info so service gets the right credit limit for this user. 332 | // Use JSONP for this browser if it doesn't support CORS 333 | return $http.get(settings) 334 | .then((data) => { 335 | // Unpack JSON data in the response object 336 | // to find maxRemainingAmount 337 | this.isCreditOk = this.total <= maxRemainingAmount 338 | }) 339 | .catch((error) => { 340 | // Interpret error 341 | // Cope w/ timeout? retry? try alternate service? 342 | // Re-reject with appropriate error for a user to see 343 | }); 344 | }; 345 | } 346 | ``` 347 | 348 | ```javascript 349 | /* recommended */ 350 | class Order { 351 | constructor (creditService) { 352 | this.isCreditOk; 353 | this.total = 0; 354 | } 355 | checkCredit() { 356 | return creditService.isOrderTotalOk(this.total) 357 | .then((isOk) => { this.isCreditOk = isOk; }) 358 | .catch(showError); 359 | } 360 | } 361 | ``` 362 | 363 | ### Keep Controllers Focused 364 | ###### [Style [Y037](#style-y037)] 365 | 366 | - 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. 367 | 368 | *Why?*: Reusing controllers with several views is brittle and good end-to-end (e2e) test coverage is required to ensure stability across large applications. 369 | 370 | ### Assigning Controllers 371 | ###### [Style [Y038](#style-y038)] 372 | 373 | - When a controller must be paired with a view and either part may be re-used by other controllers or views, define controllers along with their routes. 374 | 375 | Note: If a View is loaded via another means besides a route, then use the `ng-controller="AvengersController as avengers"` syntax. 376 | 377 | *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. 378 | 379 | ```javascript 380 | /* avoid - when using with a route and dynamic pairing is desired */ 381 | 382 | // route-config.js 383 | angular 384 | .module('app') 385 | .config(config); 386 | 387 | function config($routeProvider) { 388 | $routeProvider 389 | .when('/avengers', { 390 | templateUrl: 'avengers.html' 391 | }); 392 | } 393 | ``` 394 | 395 | ```html 396 | 397 |
398 |
399 | ``` 400 | 401 | ```javascript 402 | /* recommended */ 403 | 404 | // route-config.js 405 | angular 406 | .module('app') 407 | .config(config); 408 | 409 | function config($routeProvider) { 410 | $routeProvider 411 | .when('/avengers', { 412 | templateUrl: 'avengers.html', 413 | controller: 'AvengersController', 414 | controllerAs: 'avengers' 415 | }); 416 | } 417 | ``` 418 | 419 | ```html 420 | 421 |
422 |
423 | ``` 424 | 425 | **[Back to top](#table-of-contents)** 426 | 427 | 428 | ## Factories 429 | 430 | ### Single Responsibility 431 | ###### [Style [Y050](#style-y050)] 432 | 433 | - 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. 434 | 435 | ### Singletons 436 | ###### [Style [Y051](#style-y051)] 437 | 438 | - Factories are singletons and return an object that contains the members of the service. 439 | 440 | Note: [All Angular services are singletons](https://docs.angularjs.org/guide/services). 441 | 442 | **[Back to top](#table-of-contents)** 443 | 444 | ## Data Services 445 | 446 | ### Separate Data Calls 447 | ###### [Style [Y060](#style-y060)] 448 | 449 | - 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. 450 | 451 | *Why?*: The controller's responsibility is for the presentation and gathering of information for the view. It should not care how it gets the data, just that it knows who to ask for it. Separating the data services moves the logic on how to get it to the data service, and lets the controller be simpler and more focused on the view. 452 | 453 | *Why?*: This makes it easier to test (mock or real) the data calls when testing a controller that uses a data service. 454 | 455 | *Why?*: Data service implementation may have very specific code to handle the data repository. This may include headers, how to talk to the data, or other services such as `$http`. Separating the logic into a data service encapsulates this logic in a single place hiding the implementation from the outside consumers (perhaps a controller), also making it easier to change the implementation. 456 | 457 | ```javascript 458 | /* recommended */ 459 | 460 | // dataservice factory 461 | angular 462 | .module('app.core') 463 | .factory('dataservice', dataservice); 464 | 465 | class dataservice { 466 | constructor($http, logger) { 467 | this.$http = $http; 468 | this.logger = logger; 469 | } 470 | getAvengers() { 471 | return this.$http.get('/api/maa') 472 | .then(getAvengersComplete) 473 | .catch(getAvengersFailed); 474 | 475 | getAvengersComplete(response) => { 476 | return response.data.results; 477 | } 478 | 479 | getAvengersFailed(error) => { 480 | this.logger.error('XHR Failed for getAvengers.' + error.data); 481 | } 482 | } 483 | } 484 | ``` 485 | 486 | ### Return a Promise from Data Calls 487 | ###### [Style [Y061](#style-y061)] 488 | 489 | - When calling a data service that returns a promise such as `$http`, return a promise in your calling function too. 490 | 491 | *Why?*: You can chain the promises together and take further action after the data call completes and resolves or rejects the promise. 492 | 493 | ```javascript 494 | /* recommended */ 495 | 496 | activate() { 497 | /** 498 | * Step 1 499 | * Ask the getAvengers function for the 500 | * avenger data and wait for the promise 501 | */ 502 | return getAvengers().then(() => { 503 | /** 504 | * Step 4 505 | * Perform an action on resolve of final promise 506 | */ 507 | logger.info('Activated Avengers View'); 508 | }); 509 | } 510 | 511 | getAvengers() { 512 | /** 513 | * Step 2 514 | * Ask the data service for the data and wait 515 | * for the promise 516 | */ 517 | return dataservice.getAvengers() 518 | .then((data) => { 519 | /** 520 | * Step 3 521 | * set the data and resolve the promise 522 | */ 523 | this.avengers = data; 524 | return this.avengers; 525 | }); 526 | } 527 | ``` 528 | 529 | **[Back to top](#table-of-contents)** 530 | 531 | ## Directives 532 | ### Limit 1 Per File 533 | ###### [Style [Y070](#style-y070)] 534 | 535 | - Create one directive per file. Name the file for the directive. 536 | 537 | *Why?*: It is easy to mash all the directives in one file, but difficult to then break those out so some are shared across apps, some across modules, some just for one module. 538 | 539 | *Why?*: One directive per file is easy to maintain. 540 | 541 | > Note: "**Best Practice**: Directives should clean up after themselves. You can use `element.on('$destroy', ...)` or `scope.$on('$destroy', ...)` to run a clean-up function when the directive is removed" ... from the Angular documentation. 542 | 543 | ```javascript 544 | /* avoid */ 545 | /* directives.js */ 546 | 547 | class orderCalendarRange { 548 | /* implementation details */ 549 | } 550 | 551 | class salesCustomerInfo { 552 | /* implementation details */ 553 | } 554 | 555 | class sharedSpinner { 556 | /* implementation details */ 557 | } 558 | ``` 559 | 560 | ```javascript 561 | /* recommended */ 562 | 563 | /* calendarRange.directive.js */ 564 | 565 | /** 566 | * @desc order directive that is specific to the order module at a company named Acme 567 | * @example
568 | */ 569 | 570 | class orderCalendarRange { 571 | /* implementation details */ 572 | } 573 | ``` 574 | 575 | ```javascript 576 | /* recommended */ 577 | 578 | /* customerInfo.directive.js */ 579 | 580 | /** 581 | * @desc sales directive that can be used anywhere across the sales app at a company named Acme 582 | * @example
583 | */ 584 | 585 | class salesCustomerInfo { 586 | /* implementation details */ 587 | } 588 | ``` 589 | 590 | ```javascript 591 | /* recommended */ 592 | 593 | /* spinner.directive.js */ 594 | 595 | /** 596 | * @desc spinner directive that can be used anywhere across apps at a company named Acme 597 | * @example
598 | */ 599 | 600 | class sharedSpinner { 601 | /* implementation details */ 602 | } 603 | ``` 604 | 605 | 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. 606 | 607 | ### Manipulate DOM in a Directive 608 | ###### [Style [Y072](#style-y072)] 609 | 610 | - 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. 611 | 612 | *Why?*: DOM manipulation can be difficult to test, debug, and there are often better ways (e.g. CSS, animations, templates) 613 | 614 | ### Provide a Unique Directive Prefix 615 | ###### [Style [Y073](#style-y073)] 616 | 617 | - Provide a short, unique and descriptive directive prefix such as `acmeSalesCustomerInfo` which would be declared in HTML as `acme-sales-customer-info`. 618 | 619 | *Why?*: The unique short prefix identifies the directive's context and origin. For example a prefix of `cc-` may indicate that the directive is part of a CodeCamper app while `acme-` may indicate a directive for the Acme company. 620 | 621 | 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/). 622 | 623 | ### Restrict to Elements and Attributes 624 | ###### [Style [Y074](#style-y074)] 625 | 626 | - 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. 627 | 628 | *Why?*: It makes sense. 629 | 630 | *Why?*: While we can allow the directive to be used as a class, if the directive is truly acting as an element it makes more sense as an element or at least as an attribute. 631 | 632 | Note: EA is the default for Angular 1.3 + 633 | 634 | ```html 635 | 636 |
637 | ``` 638 | 639 | ```javascript 640 | /* avoid */ 641 | 642 | class myCalendarRange { 643 | constructor() { 644 | this.link = this.linkFunc; 645 | this.templateUrl = '/template/is/located/here.html'; 646 | this.restrict = 'C'; 647 | } 648 | linkFunc(scope, element, attrs) { 649 | /* */ 650 | } 651 | } 652 | ``` 653 | 654 | ```html 655 | 656 | 657 |
658 | ``` 659 | 660 | ```javascript 661 | /* recommended */ 662 | 663 | class myCalendarRange { 664 | constructor() { 665 | this.link = this.linkFunc; 666 | this.templateUrl = '/template/is/located/here.html'; 667 | this.restrict = 'EA'; 668 | } 669 | linkFunc(scope, element, attrs) { 670 | /* */ 671 | } 672 | } 673 | ``` 674 | 675 | ### Directives and ControllerAs 676 | ###### [Style [Y075](#style-y075)] 677 | 678 | - Use `controller as` syntax with a directive to be consistent with using `controller as` with view and controller pairings. 679 | 680 | *Why?*: It makes sense and it's not difficult. 681 | 682 | Note: The directive below demonstrates some of the ways you can use scope inside of link and directive controllers, using controllerAs. I in-lined the template just to keep it all in one place. 683 | 684 | Note: Note that the directive's controller is outside the directive's closure. This style eliminates issues where the injection gets created as unreachable code after a `return`. 685 | 686 | ```html 687 |
688 | ``` 689 | 690 | ```js 691 | //myExample.directive.js 692 | class ExampleController { 693 | constructor($scope) { 694 | // Injecting $scope just for comparison 695 | 696 | this.min = 3; 697 | 698 | console.log('CTRL: $scope.example.min = %s', $scope.example.min); 699 | console.log('CTRL: $scope.example.max = %s', $scope.example.max); 700 | console.log('CTRL: this.min = %s', this.min); 701 | console.log('CTRL: this.max = %s', this.max); 702 | } 703 | } 704 | 705 | class myExample { 706 | constructor() { 707 | this.restrict = 'EA'; 708 | this.templateUrl = 'app/feature/example.directive.html'; 709 | this.scope = { 710 | max: '=' 711 | }; 712 | this.link = this.linkFunc; 713 | this.controller = ExampleController; 714 | this.controllerAs = 'example'; 715 | this.bindToController = true; // because the scope is isolated 716 | } 717 | linkFunc(scope, el, attr, ctrl) { 718 | console.log('LINK: scope.min = %s *** should be undefined', scope.min); 719 | console.log('LINK: scope.max = %s *** should be undefined', scope.max); 720 | console.log('LINK: scope.example.min = %s', scope.example.min); 721 | console.log('LINK: scope.example.max = %s', scope.example.max); 722 | } 723 | } 724 | ``` 725 | 726 | ```html 727 | 728 |
hello world
729 |
max={{example.max}}
730 |
min={{example.min}}
731 | ``` 732 | 733 | Note: You can also name the controller when you inject it into the link function and access directive attributes as properties of the controller. 734 | 735 | ```javascript 736 | // Alternative to above example 737 | linkFunc(scope, el, attr, example) { 738 | console.log('LINK: scope.min = %s *** should be undefined', scope.min); 739 | console.log('LINK: scope.max = %s *** should be undefined', scope.max); 740 | console.log('LINK: example.min = %s', example.min); 741 | console.log('LINK: example.max = %s', example.max); 742 | } 743 | ``` 744 | 745 | ###### [Style [Y076](#style-y076)] 746 | 747 | - 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. 748 | 749 | *Why?*: It makes it easy to bind outer scope to the directive's controller scope. 750 | 751 | Note: `bindToController` was introduced in Angular 1.3.0. 752 | 753 | ```html 754 |
755 | ``` 756 | 757 | ```javascript 758 | //myExample.directive.js 759 | class ExampleController { 760 | constructor() { 761 | this.min = 3; 762 | console.log('CTRL: this.min = %s', this.min); 763 | console.log('CTRL: this.max = %s', this.max); 764 | } 765 | } 766 | 767 | class myExample { 768 | constructor() { 769 | this.restrict = 'EA'; 770 | this.templateUrl = 'app/feature/example.directive.html'; 771 | this.scope = { 772 | max: '=' 773 | }; 774 | this.link = this.linkFunc; 775 | this.controller = ExampleController; 776 | this.controllerAs = 'example'; 777 | this.bindToController = true; 778 | } 779 | } 780 | ``` 781 | 782 | ```html 783 | 784 |
hello world
785 |
max={{example.max}}
786 |
min={{example.min}}
787 | ``` 788 | 789 | **[Back to top](#table-of-contents)** 790 | 791 | ### Route Resolve Promises 792 | ###### [Style [Y081](#style-y081)] 793 | 794 | - 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. 795 | 796 | - Use a route resolve when you want to decide to cancel the route before ever transitioning to the View. 797 | 798 | *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. 799 | 800 | *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`) 801 | 802 | 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. 803 | 804 | ```javascript 805 | /* avoid */ 806 | 807 | class AvengersController { 808 | constructor(movieService) { 809 | // unresolved 810 | this.movies; 811 | // resolved asynchronously 812 | movieService.getMovies().then((response) => { 813 | this.movies = response.movies; 814 | }); 815 | } 816 | } 817 | ``` 818 | 819 | ```javascript 820 | /* better */ 821 | 822 | // route-config.js 823 | 824 | function config($routeProvider) { 825 | $routeProvider 826 | .when('/avengers', { 827 | templateUrl: 'avengers.html', 828 | controller: 'AvengersController', 829 | controllerAs: 'avengers', 830 | resolve: { 831 | moviesPrepService: function(movieService) { 832 | return movieService.getMovies(); 833 | } 834 | } 835 | }); 836 | } 837 | 838 | // avengers.controller.js 839 | 840 | class AvengersController { 841 | constructor(moviesPrepService) { 842 | this.movies = moviesPrepService.movies; 843 | } 844 | } 845 | ``` 846 | 847 | Note: The example below shows the route resolve points to a named function, which is easier to debug and easier to handle dependency injection. 848 | 849 | ```javascript 850 | /* even better */ 851 | 852 | // route-config.js 853 | 854 | function config($routeProvider) { 855 | $routeProvider 856 | .when('/avengers', { 857 | templateUrl: 'avengers.html', 858 | controller: 'AvengersController', 859 | controllerAs: 'avengers', 860 | resolve: { 861 | moviesPrepService: moviesPrepService 862 | } 863 | }); 864 | } 865 | 866 | function moviesPrepService(movieService) { 867 | return movieService.getMovies(); 868 | } 869 | 870 | // avengers.controller.js 871 | 872 | class AvengersController { 873 | constructor(moviesPrepService) { 874 | this.movies = moviesPrepService.movies; 875 | } 876 | } 877 | ``` 878 | 879 | **[Back to top](#table-of-contents)** 880 | 881 | ## Components 882 | 883 | A Component module is the container reference for all reusable components. The parts required are decoupled from all other parts and thus can be moved into any other application with ease. As with other parts, keeping the template and controller in separate files reduces component clutter. 884 | 885 | When creating components, a configuration object is supplied as opposed to a function used by directive modules. 886 | 887 | 888 | ```javascript 889 | /* avoid */ 890 | 891 | const compliment = { 892 | bindings: { 893 | userName: '@', 894 | compliment: '@', 895 | }, 896 | template: '

Hello {{$ctrl.userName}} you look {{$ctrl.compliment}}!

', 897 | controller: function () {/*controller*/} 898 | } 899 | 900 | angular.module('app') 901 | .component('compliment', compliment); 902 | 903 | ``` 904 | 905 | ```javascript 906 | /* recommended */ 907 | 908 | // import template and controller from individual component directory 909 | import controller from './compliment.controller' 910 | import template from './compliment.template.html' 911 | 912 | const compliment = { 913 | bindings: { 914 | userName: '@', 915 | compliment: '@', 916 | }, 917 | template, // template and controller using ES6 shorthand 918 | controller 919 | } 920 | 921 | angular.module('app') 922 | .component('compliment', compliment); 923 | 924 | ``` 925 | 926 | **[Back to top](#table-of-contents)** 927 | 928 | ## Minification and Annotation 929 | 930 | ### ng-annotate 931 | ###### [Style [Y100](#style-y100)] 932 | 933 | - 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'` 934 | 935 | *Why?*: This safeguards your code from any dependencies that may not be using minification-safe practices. 936 | 937 | ```javascript 938 | class Avengers { 939 | constructor(storage, avengerService) { 940 | 'ngInject'; 941 | 942 | this.heroSearch = ''; 943 | 944 | this.avengerService = avengerService; 945 | this.storage = storage; 946 | } 947 | storeHero() { 948 | let hero = this.avengerService.find(this.heroSearch); 949 | this.storage.save(hero.name, hero); 950 | } 951 | } 952 | ``` 953 | 954 | 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. 955 | 956 | ```javascript 957 | // Using @ngInject annotations 958 | function config($routeProvider) { 959 | $routeProvider 960 | .when('/avengers', { 961 | templateUrl: 'avengers.html', 962 | controller: 'AvengersController', 963 | controllerAs: 'avengers', 964 | resolve: { /* @ngInject */ 965 | moviesPrepService: function(movieService) { 966 | return movieService.getMovies(); 967 | } 968 | } 969 | }); 970 | } 971 | ``` 972 | 973 | > 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 minification 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. 974 | `` 975 | 976 | **[Back to top](#table-of-contents)** 977 | 978 | ## Exception Handling 979 | 980 | ### decorators 981 | ###### [Style [Y110](#style-y110)] 982 | 983 | - 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. 984 | 985 | *Why?*: Provides a consistent way to handle uncaught Angular exceptions for development-time or run-time. 986 | 987 | Note: Another option is to override the service instead of using a decorator. This is a fine option, but if you want to keep the default behavior and extend it a decorator is recommended. 988 | 989 | ```javascript 990 | /* recommended */ 991 | 992 | function exceptionConfig($provide) { 993 | 'ngInject'; 994 | $provide.decorator('$exceptionHandler', extendExceptionHandler); 995 | } 996 | 997 | 998 | function extendExceptionHandler($delegate, toastr) { 999 | 'ngInject'; 1000 | return function(exception, cause) { 1001 | $delegate(exception, cause); 1002 | let errorData = { 1003 | exception: exception, 1004 | cause: cause 1005 | }; 1006 | /** 1007 | * Could add the error to a service's collection, 1008 | * add errors to $rootScope, log errors to remote web server, 1009 | * or log locally. Or throw hard. It is entirely up to you. 1010 | * throw exception; 1011 | */ 1012 | toastr.error(exception.msg, errorData); 1013 | }; 1014 | } 1015 | ``` 1016 | 1017 | ### Exception Catchers 1018 | ###### [Style [Y111](#style-y111)] 1019 | 1020 | - Create a factory that exposes an interface to catch and gracefully handle exceptions. 1021 | 1022 | *Why?*: Provides a consistent way to catch exceptions that may be thrown in your code (e.g. during XHR calls or promise failures). 1023 | 1024 | Note: The exception catcher is good for catching and reacting to specific exceptions from calls that you know may throw one. For example, when making an XHR call to retrieve data from a remote web service and you want to catch any exceptions from that service and react uniquely. 1025 | 1026 | ```javascript 1027 | /* recommended */ 1028 | 1029 | class exception { 1030 | constructor(logger) { 1031 | 'ngInject'; 1032 | this.logger = logger; 1033 | } 1034 | catcher(message) { 1035 | return (reason) => { 1036 | this.logger.error(message, reason); 1037 | }; 1038 | } 1039 | } 1040 | ``` 1041 | 1042 | ### Route Errors 1043 | ###### [Style [Y112](#style-y112)] 1044 | 1045 | - Handle and log all routing errors using [`$routeChangeError`](https://docs.angularjs.org/api/ngRoute/service/$route#$routeChangeError). 1046 | 1047 | *Why?*: Provides a consistent way to handle all routing errors. 1048 | 1049 | *Why?*: Potentially provides a better user experience if a routing error occurs and you route them to a friendly screen with more details or recovery options. 1050 | 1051 | ```javascript 1052 | /* recommended */ 1053 | let handlingRouteChangeError = false; 1054 | 1055 | function handleRoutingErrors() { 1056 | /** 1057 | * Route cancellation: 1058 | * On routing error, go to the dashboard. 1059 | * Provide an exit clause if it tries to do it twice. 1060 | */ 1061 | $rootScope.$on('$routeChangeError', 1062 | function(event, current, previous, rejection) { 1063 | if (handlingRouteChangeError) { return; } 1064 | handlingRouteChangeError = true; 1065 | let destination = (current && (current.title || 1066 | current.name || current.loadedTemplateUrl)) || 1067 | 'unknown target'; 1068 | let msg = 'Error routing to ' + destination + '. ' + 1069 | (rejection.msg || ''); 1070 | 1071 | /** 1072 | * Optionally log using a custom service or $log. 1073 | * (Don't forget to inject custom service) 1074 | */ 1075 | logger.warning(msg, [current]); 1076 | 1077 | /** 1078 | * On routing error, go to another route/state. 1079 | */ 1080 | $location.path('/'); 1081 | 1082 | } 1083 | ); 1084 | } 1085 | ``` 1086 | 1087 | **[Back to top](#table-of-contents)** 1088 | 1089 | ## Naming 1090 | 1091 | ### Naming Guidelines 1092 | ###### [Style [Y120](#style-y120)] 1093 | 1094 | - Use consistent names for all parts following a pattern that describes the parts feature then (optionally) its type. My recommended pattern is `feature.type.js`. There are 2 names for most assets: 1095 | * the file name (`avengers.controller.js`) 1096 | * the registered part name with Angular (`AvengersController`) 1097 | 1098 | *Why?*: Naming conventions help provide a consistent way to find content at a glance. Consistency within the project is vital. Consistency with a team is important. Consistency across a company provides tremendous efficiency. 1099 | 1100 | *Why?*: The naming conventions should simply help you find your code faster and make it easier to understand. 1101 | 1102 | ### Feature File Names 1103 | ###### [Style [Y121](#style-y121)] 1104 | 1105 | - Use consistent names for all parts following a pattern that describes the parts feature then (optionally) its type. My recommended pattern is `feature.type.js`. 1106 | 1107 | *Why?*: Provides a consistent way to quickly identify individula parts. 1108 | 1109 | *Why?*: Provides pattern matching for any automated tasks. 1110 | 1111 | ```javascript 1112 | /** 1113 | * common options 1114 | */ 1115 | 1116 | // Controllers 1117 | avengers.js 1118 | avengers.controller.js 1119 | avengersController.js 1120 | 1121 | // Services/Factories 1122 | logger.js 1123 | logger.service.js 1124 | loggerService.js 1125 | ``` 1126 | 1127 | ```javascript 1128 | /** 1129 | * recommended 1130 | */ 1131 | 1132 | // controllers 1133 | avengers.controller.js 1134 | avengers.controller.spec.js 1135 | 1136 | // services/factories 1137 | logger.service.js 1138 | logger.service.spec.js 1139 | 1140 | // constants 1141 | constants.js 1142 | 1143 | // module definition 1144 | avengers.module.js 1145 | 1146 | // routes 1147 | avengers.routes.js 1148 | avengers.routes.spec.js 1149 | 1150 | // configuration 1151 | avengers.config.js 1152 | 1153 | // directives 1154 | avenger-profile.directive.js 1155 | avenger-profile.directive.spec.js 1156 | ``` 1157 | 1158 | 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 parts 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`. 1159 | 1160 | ```javascript 1161 | /** 1162 | * recommended 1163 | */ 1164 | // Controllers 1165 | avengers.js 1166 | avengers.spec.js 1167 | ``` 1168 | 1169 | ### Test File Names 1170 | ###### [Style [Y122](#style-y122)] 1171 | 1172 | - Name test specifications similar to the component they test with a suffix of `spec`. 1173 | 1174 | *Why?*: Provides a consistent way to quickly identify components. 1175 | 1176 | *Why?*: Provides pattern matching for [karma](http://karma-runner.github.io/) or other test runners. 1177 | 1178 | ```javascript 1179 | /** 1180 | * recommended 1181 | */ 1182 | avengers.controller.spec.js 1183 | logger.service.spec.js 1184 | avengers.routes.spec.js 1185 | avenger-profile.directive.spec.js 1186 | ``` 1187 | 1188 | ### Controller Names 1189 | ###### [Style [Y123](#style-y123)] 1190 | 1191 | - Use consistent names for all controllers named after their feature. Use UpperCamelCase for controllers, as they are classes. 1192 | 1193 | *Why?*: Provides a consistent way to quickly identify and reference controllers. 1194 | 1195 | *Why?*: UpperCamelCase is conventional for identifying object that can be instantiated using a constructor. 1196 | 1197 | ```javascript 1198 | /** 1199 | * recommended 1200 | */ 1201 | 1202 | // avengers.controller.js 1203 | 1204 | class HeroAvengersController{ 1205 | constructor() { } 1206 | } 1207 | ``` 1208 | 1209 | ### Controller Name Suffix 1210 | ###### [Style [Y124](#style-y124)] 1211 | 1212 | - Append the controller name with the suffix `Controller`. 1213 | 1214 | *Why?*: The `Controller` suffix is more commonly used and is more explicitly descriptive. 1215 | 1216 | ```javascript 1217 | /** 1218 | * recommended 1219 | */ 1220 | 1221 | // avengers.controller.js 1222 | 1223 | class AvengersController{ 1224 | constructor() { } 1225 | } 1226 | ``` 1227 | 1228 | ### Factory and Service Names 1229 | ###### [Style [Y125](#style-y125)] 1230 | 1231 | - Use consistent names for all factories and services named after their feature. Use camel-casing for services and factories. Avoid prefixing factories and services with `$`. Only suffix service and factories with `Service` when it is not clear what they are (i.e. when they are nouns). 1232 | 1233 | *Why?*: Provides a consistent way to quickly identify and reference factories. 1234 | 1235 | *Why?*: Avoids name collisions with built-in factories and services that use the `$` prefix. 1236 | 1237 | *Why?*: Clear service names such as `logger` do not require a suffix. 1238 | 1239 | *Why?*: Service names such as `avengers` are nouns and require a suffix and should be named `avengersService`. 1240 | 1241 | ```javascript 1242 | /** 1243 | * recommended 1244 | */ 1245 | 1246 | // logger.service.js 1247 | 1248 | class logger { 1249 | constructor() { } 1250 | } 1251 | ``` 1252 | 1253 | ```javascript 1254 | /** 1255 | * recommended 1256 | */ 1257 | 1258 | // credit.service.js 1259 | 1260 | class creditService { 1261 | constructor() { } 1262 | } 1263 | 1264 | // customer.service.js 1265 | 1266 | class customersService { 1267 | constructor() { } 1268 | } 1269 | ``` 1270 | 1271 | ### Directive Names 1272 | ###### [Style [Y126](#style-y126)] 1273 | 1274 | - 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). 1275 | 1276 | *Why?*: Provides a consistent way to quickly identify and reference directives. 1277 | 1278 | ```javascript 1279 | /** 1280 | * recommended 1281 | */ 1282 | 1283 | // avenger-profile.directive.js 1284 | 1285 | // usage is 1286 | 1287 | class xxAvengerProfile { 1288 | constructor() { } 1289 | } 1290 | ``` 1291 | 1292 | ### Modules 1293 | ###### [Style [Y127](#style-y127)] 1294 | 1295 | - 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`. 1296 | 1297 | *Why?*: Provides consistency for multiple module apps, and for expanding to large applications. 1298 | 1299 | *Why?*: Provides easy way to use task automation to load all module definitions first, then all other angular files (for bundling). 1300 | 1301 | ### Configuration 1302 | ###### [Style [Y128](#style-y128)] 1303 | 1304 | - 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`. 1305 | 1306 | *Why?*: Separates configuration from module definition, directives, and active code. 1307 | 1308 | *Why?*: Provides an identifiable place to set configuration for a module. 1309 | 1310 | ### Routes 1311 | ###### [Style [Y129](#style-y129)] 1312 | 1313 | - 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. 1314 | 1315 | **[Back to top](#table-of-contents)** 1316 | 1317 | ## Application Structure LIFT Principle 1318 | ### LIFT 1319 | ###### [Style [Y140](#style-y140)] 1320 | 1321 | - 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. 1322 | 1323 | *Why LIFT?*: Provides a consistent structure that scales well, is modular, and makes it easier to increase developer efficiency by finding code quickly. Another way to check your app structure is to ask yourself: How quickly can you open and work in all of the related files for a feature? 1324 | 1325 | When I find my structure is not feeling comfortable, I go back and revisit these LIFT guidelines 1326 | 1327 | 1. `L`ocating our code is easy 1328 | 2. `I`dentify code at a glance 1329 | 3. `F`lat structure as long as we can 1330 | 4. `T`ry to stay DRY (Don’t Repeat Yourself) or T-DRY 1331 | 1332 | ### Locate 1333 | ###### [Style [Y141](#style-y141)] 1334 | 1335 | - Make locating your code intuitive, simple and fast. 1336 | 1337 | *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. 1338 | 1339 | ``` 1340 | /bower_components 1341 | /client 1342 | /app 1343 | /avengers 1344 | /blocks 1345 | /exception 1346 | /logger 1347 | /core 1348 | /dashboard 1349 | /data 1350 | /layout 1351 | /widgets 1352 | /content 1353 | index.html 1354 | .bower.json 1355 | ``` 1356 | 1357 | ### Identify 1358 | ###### [Style [Y142](#style-y142)] 1359 | 1360 | - When you look at a file you should instantly know what it contains and represents. 1361 | 1362 | *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 part. 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. 1363 | 1364 | ### Flat 1365 | ###### [Style [Y143](#style-y143)] 1366 | 1367 | - Keep a flat folder structure as long as possible. When you get to 7+ files, begin considering separation. 1368 | 1369 | *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. 1370 | 1371 | ### T-DRY (Try to Stick to DRY) 1372 | ###### [Style [Y144](#style-y144)] 1373 | 1374 | - Be DRY, but don't go nuts and sacrifice readability. 1375 | 1376 | *Why?*: Being DRY is important, but not crucial if it sacrifices the others in LIFT, which is why I call it T-DRY. I don’t want to type session-view.html for a view because, well, it’s obviously a view. If it is not obvious or by convention, then I name it. 1377 | 1378 | **[Back to top](#table-of-contents)** 1379 | 1380 | ## Application Structure 1381 | 1382 | ### Overall Guidelines 1383 | ###### [Style [Y150](#style-y150)] 1384 | 1385 | - Have a near term view of implementation and a long term vision. In other words, start small 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`). 1386 | 1387 | Note: Find more details and reasoning behind the structure at [this original post on application structure](http://www.johnpapa.net/angular-app-structuring-guidelines/). 1388 | 1389 | 1390 | ### Folders-by-Feature Structure 1391 | ###### [Style [Y152](#style-y152)] 1392 | 1393 | - 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. 1394 | 1395 | *Why?*: A developer can locate the code, identify what each file represents at a glance, the structure is flat as can be, and there is no repetitive nor redundant names. 1396 | 1397 | *Why?*: The LIFT guidelines are all covered. 1398 | 1399 | *Why?*: Helps reduce the app from becoming cluttered through organizing the content and keeping them aligned with the LIFT guidelines. 1400 | 1401 | *Why?*: When there are a lot of files (10+) locating them is easier with a consistent folder structures and more difficult in flat structures. 1402 | 1403 | ```javascript 1404 | /** 1405 | * recommended 1406 | */ 1407 | 1408 | app/ 1409 | app.module.js 1410 | app.config.js 1411 | componenets/ 1412 | compliment/ 1413 | compliment.component.js 1414 | compliment.template.html 1415 | compliment.controller.js 1416 | compliment.spec.js 1417 | directives/ 1418 | calendar.directive.js 1419 | calendar.directive.html 1420 | user-profile.directive.js 1421 | user-profile.directive.html 1422 | people/ 1423 | attendees.html 1424 | attendees.controller.js 1425 | people.routes.js 1426 | speakers.html 1427 | speakers.controller.js 1428 | speaker-detail.html 1429 | speaker-detail.controller.js 1430 | services/ 1431 | data.service.js 1432 | localstorage.service.js 1433 | logger.service.js 1434 | spinner.service.js 1435 | sessions/ 1436 | sessions.html 1437 | sessions.controller.js 1438 | sessions.routes.js 1439 | session-detail.html 1440 | session-detail.controller.js 1441 | ``` 1442 | 1443 | ![Sample App Structure](https://raw.githubusercontent.com/rwwagner90/angular-styleguide/master/assets/modularity-2.png) 1444 | 1445 | Note: Do not structure your app 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. 1446 | 1447 | ```javascript 1448 | /* 1449 | * avoid 1450 | * Alternative folders-by-type. 1451 | * I recommend "folders-by-feature", instead. 1452 | */ 1453 | 1454 | app/ 1455 | app.module.js 1456 | app.config.js 1457 | app.routes.js 1458 | directives.js 1459 | controllers/ 1460 | attendees.js 1461 | session-detail.js 1462 | sessions.js 1463 | shell.js 1464 | speakers.js 1465 | speaker-detail.js 1466 | topnav.js 1467 | directives/ 1468 | calendar.directive.js 1469 | calendar.directive.html 1470 | user-profile.directive.js 1471 | user-profile.directive.html 1472 | services/ 1473 | dataservice.js 1474 | localstorage.js 1475 | logger.js 1476 | spinner.js 1477 | views/ 1478 | attendees.html 1479 | session-detail.html 1480 | sessions.html 1481 | shell.html 1482 | speakers.html 1483 | speaker-detail.html 1484 | topnav.html 1485 | ``` 1486 | 1487 | **[Back to top](#table-of-contents)** 1488 | 1489 | ## Modularity 1490 | 1491 | ### Many Small, Self Contained Modules 1492 | ###### [Style [Y160](#style-y160)] 1493 | 1494 | - Create small modules that encapsulate one responsibility. 1495 | 1496 | *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. 1497 | 1498 | ### Create an App Module 1499 | ###### [Style [Y161](#style-y161)] 1500 | 1501 | - Create an application root module whose role is pull together all of the modules and features of your application. Name this for your application. 1502 | 1503 | *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. 1504 | 1505 | ### Keep the App Module Thin 1506 | ###### [Style [Y162](#style-y162)] 1507 | 1508 | - Only put logic for pulling together the app in the application module. Leave features in their own modules. 1509 | 1510 | *Why?*: Adding additional roles to the application root to get remote data, display views, or other logic not related to pulling the app together muddies the app module and make both sets of features harder to reuse or turn off. 1511 | 1512 | *Why?*: The app module becomes a manifest that describes which modules help define the application. 1513 | 1514 | ### Feature Areas are Modules 1515 | ###### [Style [Y163](#style-y163)] 1516 | 1517 | - Create modules that represent feature areas, such as layout, reusable and shared services, dashboards, and app specific features (e.g. customers, admin, sales). 1518 | 1519 | *Why?*: Self contained modules can be added to the application with little or no friction. 1520 | 1521 | *Why?*: Sprints or iterations can focus on feature areas and turn them on at the end of the sprint or iteration. 1522 | 1523 | *Why?*: Separating feature areas into modules makes it easier to test the modules in isolation and reuse code. 1524 | 1525 | ### Reusable Blocks are Modules 1526 | ###### [Style [Y164](#style-y164)] 1527 | 1528 | - Create modules that represent reusable application blocks for common services such as exception handling, logging, diagnostics, security, and local data stashing. 1529 | 1530 | *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. 1531 | 1532 | ### Module Dependencies 1533 | ###### [Style [Y165](#style-y165)] 1534 | 1535 | - The application root module depends on the app specific feature modules and any shared or reusable modules. 1536 | 1537 | ![Modularity and Dependencies](https://raw.githubusercontent.com/rwwagner90/angular-styleguide/master/assets/modularity-1.png) 1538 | 1539 | *Why?*: The main app module contains a quickly identifiable manifest of the application's features. 1540 | 1541 | *Why?*: Each feature area contains a manifest of what it depends on, so it can be pulled in as a dependency in other applications and still work. 1542 | 1543 | *Why?*: Intra-App features such as shared data services become easy to locate and share from within `app.core` (choose your favorite name for this module). 1544 | 1545 | Note: This is a strategy for consistency. There are many good options here. Choose one that is consistent, follows Angular's dependency rules, and is easy to maintain and scale. 1546 | 1547 | > My structures vary slightly between projects but they all follow these guidelines for structure and modularity. The implementation may vary depending on the features and the team. In other words, don't get hung up on an exact like-for-like structure but do justify your structure using consistency, maintainability, and efficiency in mind. 1548 | 1549 | > In a small app, you can also consider putting all the shared dependencies in the app module where the feature modules have no direct dependencies. This makes it easier to maintain the smaller application, but makes it harder to reuse modules outside of this application. 1550 | 1551 | **[Back to top](#table-of-contents)** 1552 | 1553 | ## Startup Logic 1554 | 1555 | ### Run Blocks 1556 | ###### [Style [Y171](#style-y171)] 1557 | 1558 | - 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). 1559 | 1560 | *Why?*: Code directly in a run block can be difficult to test. Placing in a factory makes it easier to abstract and mock. 1561 | 1562 | ```javascript 1563 | angular 1564 | .module('app') 1565 | .run(runBlock); 1566 | 1567 | function runBlock(authenticator, translator) { 1568 | 'ngInject'; 1569 | authenticator.initialize(); 1570 | translator.initialize(); 1571 | } 1572 | ``` 1573 | 1574 | **[Back to top](#table-of-contents)** 1575 | 1576 | ## Angular $ Wrapper Services 1577 | 1578 | ### $document and $window 1579 | ###### [Style [Y180](#style-y180)] 1580 | 1581 | - 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`. 1582 | 1583 | *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. 1584 | 1585 | ### $timeout and $interval 1586 | ###### [Style [Y181](#style-y181)] 1587 | 1588 | - 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` . 1589 | 1590 | *Why?*: These services are wrapped by Angular and more easily testable and handle Angular's digest cycle thus keeping data binding in sync. 1591 | 1592 | **[Back to top](#table-of-contents)** 1593 | 1594 | ## Testing 1595 | Unit testing helps maintain clean code, as such I included some of my recommendations for unit testing foundations with links for more information. 1596 | 1597 | ### Write Tests with Stories 1598 | ###### [Style [Y190](#style-y190)] 1599 | 1600 | - 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. 1601 | 1602 | *Why?*: Writing the test descriptions helps clearly define what your story will do, will not do, and how you can measure success. 1603 | 1604 | ```javascript 1605 | it('should have Avengers controller', function() { 1606 | // TODO 1607 | }); 1608 | 1609 | it('should find 1 Avenger when filtered by name', function() { 1610 | // TODO 1611 | }); 1612 | 1613 | it('should have 10 Avengers', function() { 1614 | // TODO (mock data?) 1615 | }); 1616 | 1617 | it('should return Avengers via XHR', function() { 1618 | // TODO ($httpBackend?) 1619 | }); 1620 | 1621 | // and so on 1622 | ``` 1623 | 1624 | ### Testing Library 1625 | ###### [Style [Y191](#style-y191)] 1626 | 1627 | - Use [Jasmine](http://jasmine.github.io/) or [Mocha](http://mochajs.org) for unit testing. 1628 | 1629 | *Why?*: Both Jasmine and Mocha are widely used in the Angular community. Both are stable, well maintained, and provide robust testing features. 1630 | 1631 | Note: When using Mocha, also consider choosing an assert library such as [Chai](http://chaijs.com). I prefer Mocha. 1632 | 1633 | ### Test Runner 1634 | ###### [Style [Y192](#style-y192)] 1635 | 1636 | - Use [Karma](http://karma-runner.github.io) as a test runner. 1637 | 1638 | *Why?*: Karma is easy to configure to run once or automatically when you change your code. 1639 | 1640 | *Why?*: Karma hooks into your Continuous Integration process easily on its own or through Grunt or Gulp. 1641 | 1642 | *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). 1643 | 1644 | *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). When using Gulp, use [Karma](https://github.com/karma-runner/karma) directly and not with a plugin as the API can be called directly. 1645 | 1646 | ```javascript 1647 | /* recommended */ 1648 | 1649 | // Gulp example with Karma directly 1650 | function startTests(singleRun, done) { 1651 | var child; 1652 | var excludeFiles = []; 1653 | var fork = require('child_process').fork; 1654 | var karma = require('karma').server; 1655 | var serverSpecs = config.serverIntegrationSpecs; 1656 | 1657 | if (args.startServers) { 1658 | log('Starting servers'); 1659 | var savedEnv = process.env; 1660 | savedEnv.NODE_ENV = 'dev'; 1661 | savedEnv.PORT = 8888; 1662 | child = fork(config.nodeServer); 1663 | } else { 1664 | if (serverSpecs && serverSpecs.length) { 1665 | excludeFiles = serverSpecs; 1666 | } 1667 | } 1668 | 1669 | karma.start({ 1670 | configFile: __dirname + '/karma.conf.js', 1671 | exclude: excludeFiles, 1672 | singleRun: !!singleRun 1673 | }, karmaCompleted); 1674 | 1675 | //////////////// 1676 | 1677 | function karmaCompleted(karmaResult) { 1678 | log('Karma completed'); 1679 | if (child) { 1680 | log('shutting down the child process'); 1681 | child.kill(); 1682 | } 1683 | if (karmaResult === 1) { 1684 | done('karma: tests failed with code ' + karmaResult); 1685 | } else { 1686 | done(); 1687 | } 1688 | } 1689 | } 1690 | ``` 1691 | 1692 | ### Stubbing and Spying 1693 | ###### [Style [Y193](#style-y193)] 1694 | 1695 | - Use [Sinon](http://sinonjs.org/) for stubbing and spying. 1696 | 1697 | *Why?*: Sinon works well with both Jasmine and Mocha and extends the stubbing and spying features they offer. 1698 | 1699 | *Why?*: Sinon makes it easier to toggle between Jasmine and Mocha, if you want to try both. 1700 | 1701 | *Why?*: Sinon has descriptive messages when tests fail the assertions. 1702 | 1703 | ### Headless Browser 1704 | ###### [Style [Y194](#style-y194)] 1705 | 1706 | - Use [PhantomJS](http://phantomjs.org/) to run your tests on a server. 1707 | 1708 | *Why?*: PhantomJS is a headless browser that helps run your tests without needing a "visual" browser. So you do not have to install Chrome, Safari, IE, or other browsers on your server. 1709 | 1710 | Note: You should still test on all browsers in your environment, as appropriate for your target audience. 1711 | 1712 | ### Code Analysis 1713 | ###### [Style [Y195](#style-y195)] 1714 | 1715 | - Run ESLint on your tests. 1716 | 1717 | *Why?*: Tests are code. ESLint can help identify code quality issues that may cause the test to work improperly. 1718 | 1719 | ### Organizing Tests 1720 | ###### [Style [Y197](#style-y197)] 1721 | 1722 | - 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. 1723 | 1724 | *Why?*: Unit tests have a direct correlation to a specific component and file in source code. 1725 | 1726 | *Why?*: It is easier to keep them up to date since they are always in sight. When coding whether you do TDD or test during development or test after development, the specs are side-by-side and never out of sight nor mind, and thus more likely to be maintained which also helps maintain code coverage. 1727 | 1728 | *Why?*: When you update source code it is easier to go update the tests at the same time. 1729 | 1730 | *Why?*: Placing them side-by-side makes it easy to find them and easy to move them with the source code if you move the source. 1731 | 1732 | *Why?*: Having the spec nearby makes it easier for the source code reader to learn how the component is supposed to be used and to discover its known limitations. 1733 | 1734 | *Why?*: Separating specs so they are not in a distributed build is easy with grunt or gulp. 1735 | 1736 | ``` 1737 | /src/client/app/customers/customer-detail.controller.js 1738 | /customer-detail.controller.spec.js 1739 | /customers.controller.js 1740 | /customers.controller.spec.js 1741 | /customers.module.js 1742 | /customers.route.js 1743 | /customers.route.spec.js 1744 | ``` 1745 | 1746 | **[Back to top](#table-of-contents)** 1747 | 1748 | ## Animations 1749 | 1750 | ### Usage 1751 | ###### [Style [Y210](#style-y210)] 1752 | 1753 | - 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. 1754 | 1755 | *Why?*: Subtle animations can improve User Experience when used appropriately. 1756 | 1757 | *Why?*: Subtle animations can improve perceived performance as views transition. 1758 | 1759 | ### Sub Second 1760 | ###### [Style [Y211](#style-y211)] 1761 | 1762 | - Use short durations for animations. I generally start with 300ms and adjust until appropriate. 1763 | 1764 | *Why?*: Long animations can have the reverse affect on User Experience and perceived performance by giving the appearance of a slow application. 1765 | 1766 | ### animate.css 1767 | ###### [Style [Y212](#style-y212)] 1768 | 1769 | - Use [animate.css](http://daneden.github.io/animate.css/) for conventional animations. 1770 | 1771 | *Why?*: The animations that animate.css provides are fast, smooth, and easy to add to your application. 1772 | 1773 | *Why?*: Provides consistency in your animations. 1774 | 1775 | *Why?*: animate.css is widely used and tested. 1776 | 1777 | Note: See this [great post by Matias Niemelä on Angular animations](http://www.yearofmoo.com/2013/08/remastered-animation-in-angularjs-1-2.html) 1778 | 1779 | **[Back to top](#table-of-contents)** 1780 | 1781 | ## Comments 1782 | 1783 | ### jsDoc 1784 | ###### [Style [Y220](#style-y220)] 1785 | 1786 | - 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. 1787 | 1788 | *Why?*: You can generate (and regenerate) documentation from your code, instead of writing it from scratch. 1789 | 1790 | *Why?*: Provides consistency using a common industry tool. 1791 | 1792 | ```javascript 1793 | /** 1794 | * Logger Factory 1795 | * @namespace Factories 1796 | */ 1797 | (function() { 1798 | angular 1799 | .module('app') 1800 | .factory('logger', logger); 1801 | 1802 | /** 1803 | * @namespace Logger 1804 | * @desc Application wide logger 1805 | * @memberOf Factories 1806 | */ 1807 | function logger($log) { 1808 | var service = { 1809 | logError: logError 1810 | }; 1811 | return service; 1812 | 1813 | //////////// 1814 | 1815 | /** 1816 | * @name logError 1817 | * @desc Logs errors 1818 | * @param {String} msg Message to log 1819 | * @returns {String} 1820 | * @memberOf Factories.Logger 1821 | */ 1822 | function logError(msg) { 1823 | var loggedMsg = 'Error: ' + msg; 1824 | $log.error(loggedMsg); 1825 | return loggedMsg; 1826 | }; 1827 | } 1828 | })(); 1829 | ``` 1830 | 1831 | **[Back to top](#table-of-contents)** 1832 | 1833 | ## ESLint 1834 | 1835 | ### Use an Options File 1836 | ###### [Style [Y230](#style-y230)] 1837 | 1838 | - Use ESLint for linting your JavaScript and be sure to customize the .eslintrc file and include in source control. See the [ESLint docs](http://eslint.org/docs/rules/) for details on the options. 1839 | 1840 | *Why?*: Provides a first alert prior to committing any code to source control. 1841 | 1842 | *Why?*: Provides consistency across your team. 1843 | 1844 | *Why?*: ESLint supports ES6. 1845 | 1846 | ```javascript 1847 | { 1848 | "globals": { 1849 | "_": false, 1850 | "angular": false, 1851 | "console": false, 1852 | "inject": false, 1853 | "module": false, 1854 | "window": false 1855 | }, 1856 | "parser": "babel-eslint", 1857 | "rules": { 1858 | "brace-style": [2, "stroustrup", {"allowSingleLine": false}], 1859 | "camelcase": 1, 1860 | "comma-dangle": [1, "never"], 1861 | "curly": 1, 1862 | "dot-notation": 1, 1863 | "eqeqeq": 1, 1864 | "indent": [1, 2], 1865 | "lines-around-comment": [2, {"allowBlockStart": true, "beforeBlockComment": true, "beforeLineComment": true}], 1866 | "new-parens": 1, 1867 | "no-bitwise": 1, 1868 | "no-cond-assign": 1, 1869 | "no-debugger": 1, 1870 | "no-dupe-args": 1, 1871 | "no-dupe-keys": 1, 1872 | "no-empty": 1, 1873 | "no-invalid-regexp": 1, 1874 | "no-invalid-this": 1, 1875 | "no-mixed-spaces-and-tabs": [1, "smart-tabs"], 1876 | "no-multiple-empty-lines": [1, {"max": 2}], 1877 | "no-undef": 1, 1878 | "no-underscore-dangle": 1, 1879 | "no-unreachable": 1, 1880 | "no-unused-vars": 1, 1881 | "one-var": [1, "never"], 1882 | "quote-props": [1, "as-needed"], 1883 | "semi": [1, "always"], 1884 | "space-after-keywords": [1, "always"], 1885 | "space-unary-ops": [1, {"words": true, "nonwords": false}], 1886 | "strict": [1, "function"], 1887 | "vars-on-top": 1, 1888 | "wrap-iife": [1, "outside"], 1889 | "yoda": [1, "never"], 1890 | 1891 | //ES6 Stuff 1892 | "arrow-parens": 1, 1893 | "arrow-spacing": 1, 1894 | "constructor-super": 1, 1895 | "no-class-assign": 1, 1896 | "no-const-assign": 1, 1897 | "no-dupe-class-members": 1, 1898 | "no-this-before-super": 1, 1899 | "no-var": 1, 1900 | "object-shorthand": 1, 1901 | "prefer-arrow-callback": 1, 1902 | "prefer-const": 1 1903 | } 1904 | } 1905 | 1906 | ``` 1907 | **[Back to top](#table-of-contents)** 1908 | 1909 | ## Constants 1910 | 1911 | ### Vendor Globals 1912 | ###### [Style [Y240](#style-y240)] 1913 | 1914 | - Create an Angular Constant for vendor libraries' global variables. 1915 | 1916 | *Why?*: Provides a way to inject vendor libraries that otherwise are globals. This improves code testability by allowing you to more easily know what the dependencies of your constants are (avoids leaky abstractions). It also allows you to mock these dependencies, where it makes sense. 1917 | 1918 | ```javascript 1919 | // constants.js 1920 | 1921 | /* global toastr:false, moment:false */ 1922 | (function() { 1923 | 'use strict'; 1924 | 1925 | angular 1926 | .module('app.core') 1927 | .constant('toastr', toastr) 1928 | .constant('moment', moment); 1929 | })(); 1930 | ``` 1931 | 1932 | ###### [Style [Y241](#style-y241)] 1933 | 1934 | - 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. 1935 | 1936 | *Why?*: A value that may change, even infrequently, should be retrieved from a service so you do not have to change the source code. For example, a url for a data service could be placed in a constants but a better place would be to load it from a web service. 1937 | 1938 | *Why?*: Constants can be injected into any angular component, including providers. 1939 | 1940 | *Why?*: When an application is separated into modules that may be reused in other applications, each stand-alone module should be able to operate on its own including any dependent constants. 1941 | 1942 | ```javascript 1943 | // Constants used by the entire app 1944 | angular 1945 | .module('app.core') 1946 | .constant('moment', moment); 1947 | 1948 | // Constants used only by the sales module 1949 | angular 1950 | .module('app.sales') 1951 | .constant('events', { 1952 | ORDER_CREATED: 'event_order_created', 1953 | INVENTORY_DEPLETED: 'event_inventory_depleted' 1954 | }); 1955 | ``` 1956 | 1957 | **[Back to top](#table-of-contents)** 1958 | 1959 | 1960 | ## Yeoman Generator 1961 | ###### [Style [Y260](#style-y260)] 1962 | 1963 | You can use the [generator-gulp-angular](https://github.com/Swiip/generator-gulp-angular) to create an app that serves as a starting point for Angular that follows this style guide. 1964 | 1965 | 1. Install generator-gulp-angular 1966 | 1967 | ``` 1968 | npm install -g generator-gulp-angular 1969 | ``` 1970 | 1971 | 2. Create a new folder and change directory to it 1972 | 1973 | ``` 1974 | mkdir myapp 1975 | cd myapp 1976 | ``` 1977 | 1978 | 3. Run the generator 1979 | 1980 | ``` 1981 | yo gulp-angular 1982 | ``` 1983 | 1984 | **[Back to top](#table-of-contents)** 1985 | 1986 | ## Routing 1987 | Client-side routing is important for creating a navigation flow between views and composing views that are made of many smaller templates and directives. 1988 | 1989 | ###### [Style [Y270](#style-y270)] 1990 | 1991 | - Use the [AngularUI Router](http://angular-ui.github.io/ui-router/) for client-side routing. 1992 | 1993 | *Why?*: UI Router offers all the features of the Angular router plus a few additional ones including nested routes and states. 1994 | 1995 | *Why?*: The syntax is quite similar to the Angular router and is easy to migrate to UI Router. 1996 | 1997 | - Note: You can use a provider such as the `routerHelperProvider` shown below to help configure states across files, during the run phase. 1998 | 1999 | ```javascript 2000 | // customers.routes.js 2001 | angular 2002 | .module('app.customers') 2003 | .run(appRun); 2004 | 2005 | 2006 | function appRun(routerHelper) { 2007 | 'ngInject'; 2008 | routerHelper.configureStates(getStates()); 2009 | } 2010 | 2011 | function getStates() { 2012 | return [ 2013 | { 2014 | state: 'customer', 2015 | config: { 2016 | abstract: true, 2017 | template: '', 2018 | url: '/customer' 2019 | } 2020 | } 2021 | ]; 2022 | } 2023 | ``` 2024 | 2025 | ```javascript 2026 | // routerHelperProvider.js 2027 | angular 2028 | .module('blocks.router') 2029 | .provider('routerHelper', routerHelperProvider); 2030 | 2031 | 2032 | function routerHelperProvider($locationProvider, $stateProvider, $urlRouterProvider) { 2033 | 'ngInject'; 2034 | /* jshint validthis:true */ 2035 | this.$get = RouterHelper; 2036 | 2037 | $locationProvider.html5Mode(true); 2038 | function RouterHelper($state) { 2039 | 'ngInject'; 2040 | var hasOtherwise = false; 2041 | var service = { 2042 | configureStates: configureStates, 2043 | getStates: getStates 2044 | }; 2045 | 2046 | return service; 2047 | 2048 | /////////////// 2049 | 2050 | function configureStates(states, otherwisePath) { 2051 | states.forEach((state) => { 2052 | $stateProvider.state(state.state, state.config); 2053 | }); 2054 | if (otherwisePath && !hasOtherwise) { 2055 | hasOtherwise = true; 2056 | $urlRouterProvider.otherwise(otherwisePath); 2057 | } 2058 | } 2059 | 2060 | function getStates() { return $state.get(); } 2061 | } 2062 | } 2063 | ``` 2064 | 2065 | ###### [Style [Y271](#style-y271)] 2066 | 2067 | - Define routes for views in the module where they exist. Each module should contain the routes for the views in the module. 2068 | 2069 | *Why?*: Each module should be able to stand on its own. 2070 | 2071 | *Why?*: When removing a module or adding a module, the app will only contain routes that point to existing views. 2072 | 2073 | *Why?*: This makes it easy to enable or disable portions of an application without concern over orphaned routes. 2074 | 2075 | **[Back to top](#table-of-contents)** 2076 | 2077 | ## Task Automation 2078 | 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. 2079 | 2080 | > Learn more about gulp and patterns for task automation in my [Gulp Pluralsight course](http://jpapa.me/gulpps) 2081 | 2082 | ###### [Style [Y400](#style-y400)] 2083 | 2084 | - Use task automation to list module definition files `*.module.js` before all other application JavaScript files. 2085 | 2086 | *Why?*: Angular needs the module definitions to be registered before they are used. 2087 | 2088 | *Why?*: Naming modules with a specific pattern such as `*.module.js` makes it easy to grab them with a glob and list them first. 2089 | 2090 | ```javascript 2091 | var clientApp = './src/client/app/'; 2092 | 2093 | // Always grab module files first 2094 | var files = [ 2095 | clientApp + '**/*.module.js', 2096 | clientApp + '**/*.js' 2097 | ]; 2098 | ``` 2099 | 2100 | **[Back to top](#table-of-contents)** 2101 | 2102 | ## Filters 2103 | 2104 | ###### [Style [Y420](#style-y420)] 2105 | 2106 | - Avoid using filters for scanning all properties of a complex object graph. Use filters for select properties. 2107 | 2108 | *Why?*: Filters can easily be abused and negatively affect performance if not used wisely, for example when a filter hits a large and deep object graph. 2109 | 2110 | **[Back to top](#table-of-contents)** 2111 | 2112 | ## Angular docs 2113 | For anything else, API reference, check the [Angular documentation](//docs.angularjs.org/api). 2114 | 2115 | ## Contributing 2116 | 2117 | Open an issue first to discuss potential changes/additions. If you have questions with the guide, feel free to leave them as issues in the repository. If you find a typo, create a pull request. The idea is to keep the content up to date and use github’s native feature to help tell the story with issues and PR’s, which are all searchable via google. Why? Because odds are if you have a question, someone else does too! You can learn more here at about how to contribute. 2118 | 2119 | *By contributing to this repository you are agreeing to make your content available subject to the license of this repository.* 2120 | 2121 | ### Process 2122 | 1. Discuss the changes in a GitHub issue. 2123 | 2. Open a Pull Request, reference the issue, and explain the change and why it adds value. 2124 | 3. The Pull Request will be evaluated and either merged or declined. 2125 | 2126 | ## License 2127 | 2128 | _tldr; Use this guide. Attributions are appreciated._ 2129 | 2130 | ### Copyright 2131 | 2132 | Copyright (c) 2014-2016 [John Papa](http://johnpapa.net) 2133 | and 2015 Robert Wagner / Mike Erickson 2134 | 2135 | ### (The MIT License) 2136 | Permission is hereby granted, free of charge, to any person obtaining 2137 | a copy of this software and associated documentation files (the 2138 | 'Software'), to deal in the Software without restriction, including 2139 | without limitation the rights to use, copy, modify, merge, publish, 2140 | distribute, sublicense, and/or sell copies of the Software, and to 2141 | permit persons to whom the Software is furnished to do so, subject to 2142 | the following conditions: 2143 | 2144 | The above copyright notice and this permission notice shall be 2145 | included in all copies or substantial portions of the Software. 2146 | 2147 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 2148 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 2149 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 2150 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 2151 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 2152 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 2153 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 2154 | 2155 | **[Back to top](#table-of-contents)** 2156 | -------------------------------------------------------------------------------- /assets/above-the-fold-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobbieTheWagner/angular-styleguide-es6/98ba87dfc8b97858ca5c55e83b9c5e6c123af610/assets/above-the-fold-1.png -------------------------------------------------------------------------------- /assets/above-the-fold-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobbieTheWagner/angular-styleguide-es6/98ba87dfc8b97858ca5c55e83b9c5e6c123af610/assets/above-the-fold-2.png -------------------------------------------------------------------------------- /assets/brackets-angular-snippets.yaml: -------------------------------------------------------------------------------- 1 | - trigger: ngfilter 2 | description: "Filter" 3 | scope: javascript 4 | text: | 5 | (function () { 6 | 'use strict'; 7 | 8 | angular 9 | .module('${1:module}') 10 | .filter('${2:filter}', ${2:filter}); 11 | 12 | function ${2:filter}() { 13 | ${4:} 14 | return ${2:filter}Filter; 15 | 16 | //////////////// 17 | 18 | function ${2:filter}Filter(${3:parameters}) { 19 | return ${3:parameters}; 20 | }; 21 | } 22 | })(); 23 | 24 | - trigger: ngservice 25 | description: "Service" 26 | scope: javascript 27 | text: | 28 | (function () { 29 | 'use strict'; 30 | 31 | angular 32 | .module('${1:module}') 33 | .service('${2:Service}', ${2:Service}); 34 | 35 | ${2:Service}.$inject = ['${3:dependencies}']; 36 | 37 | /* @ngInject */ 38 | function ${2:Service}(${3:dependencies}) { 39 | this.${4:func} = ${4:func}; 40 | 41 | //////////////// 42 | 43 | function ${4:func}() { 44 | ${5:} 45 | } 46 | } 47 | })(); 48 | 49 | - trigger: ngapp 50 | description: "Module definition" 51 | scope: javascript 52 | text: | 53 | (function () { 54 | 'use strict'; 55 | 56 | angular 57 | .module('${1:module}', [ 58 | '${2:dependencies}' 59 | ]); 60 | })(); 61 | 62 | - trigger: ngfactory 63 | description: "Factory" 64 | scope: javascript 65 | text: | 66 | (function () { 67 | 'use strict'; 68 | angular 69 | .module('${1:module}') 70 | .factory('${2:factory}', ${2:factory}); 71 | 72 | ${2:factory}.$inject = ['${3:dependencies}']; 73 | 74 | /* @ngInject */ 75 | function ${2: Factory}(${3:dependencies}){ 76 | var exports = { 77 | ${4:func}: ${4:func} 78 | }; 79 | ${5:} 80 | 81 | return exports; 82 | 83 | //////////////// 84 | 85 | function ${4:func}() { 86 | } 87 | } 88 | })(); 89 | 90 | - trigger: ngdirective 91 | description: "Directive" 92 | scope: javascript 93 | text: | 94 | (function () { 95 | 'use strict'; 96 | 97 | angular 98 | .module('${1:module}') 99 | .directive('${2:directive}', ${2:directive}); 100 | 101 | ${2:directive}.$inject = ['${3:dependencies}']; 102 | 103 | /* @ngInject */ 104 | function ${2: directive}(${3:dependencies}) { 105 | // Usage: 106 | // 107 | // Creates: 108 | // 109 | var directive = { 110 | bindToController: true, 111 | controller: ${4:Controller}, 112 | controllerAs: '${5:vm}', 113 | link: link, 114 | restrict: 'A', 115 | scope: {} 116 | }; 117 | return directive; 118 | 119 | function link(scope, element, attrs, controller) { 120 | ${7:} 121 | } 122 | } 123 | 124 | ${4:Controller}.$inject = ['${6:dependencies}']; 125 | 126 | /* @ngInject */ 127 | function ${4:Controller}(${6:dependencies}) { 128 | } 129 | })(); 130 | 131 | - trigger: ngcontroller 132 | description: "Controller" 133 | scope: javascript 134 | text: | 135 | (function() { 136 | 'use strict'; 137 | 138 | angular 139 | .module('${1:module}') 140 | .controller('${2:Controller}', ${2:Controller}); 141 | 142 | ${2:Controller}.$inject = ['${3:dependencies}']; 143 | 144 | /* @ngInject */ 145 | function ${2:Controller}(${3:dependencies}){ 146 | var vm = this; 147 | vm.${4:property} = '${2:Controller}'; 148 | ${5:} 149 | 150 | activate(); 151 | 152 | //////////////// 153 | 154 | function activate() { 155 | } 156 | } 157 | })(); 158 | 159 | - trigger: ngwhen 160 | description: "ngRoute 'when'" 161 | scope: javascript 162 | text: | 163 | .when('/${1:url}', { 164 | templateUrl: '${2:template}.html', 165 | controller: '${3:Controller}', 166 | controllerAs: '${4:vm}' 167 | })${5:} 168 | 169 | - trigger: ngstate 170 | description: "UI-Router state" 171 | scope: javascript 172 | text: | 173 | .state('${1:state}', { 174 | url: '${2:/url}' 175 | templateUrl: '${3:template}.html', 176 | controller: '${4:Controller}' 177 | controllerAs: '${5:vm}' 178 | })${6:} 179 | 180 | - trigger: ngmodule 181 | description: "Module getter" 182 | scope: javascript 183 | text: | 184 | angular 185 | .module('${1:module}')${2:} 186 | - trigger: ngconst 187 | description: "Constant" 188 | scope: javascript 189 | text: | 190 | .constant('${1:name}', ${2:value}); 191 | 192 | - trigger: ngvalue 193 | description: "Value" 194 | scope: javascript 195 | text: | 196 | .value('${1:name}', ${2:value}); 197 | 198 | - trigger: ngconfig 199 | description: "Config phase function" 200 | scope: javascript 201 | text: | 202 | .config(${1:configuration}) 203 | 204 | ${1:configuration}.$inject = ['${2:dependencies}']; 205 | 206 | /* @ngInject */ 207 | function ${1:configuration} (${2:dependencies}) { 208 | ${3:} 209 | } 210 | 211 | - trigger: ngrun 212 | description: "Run phase function" 213 | scope: javascript 214 | text: | 215 | .run(${1:runFn}) 216 | 217 | ${1:runFn}.$inject = ['${2:dependencies}']; 218 | 219 | /* @ngInject */ 220 | function ${1:runFn} (${2:dependencies}) { 221 | ${3:} 222 | } 223 | 224 | - trigger: ngtranslate 225 | description: "$translate service" 226 | scope: javascript 227 | text: | 228 | $translate(['${1:key1}']).then(function(translations){ 229 | ${2:value} = translations['${3:key1}']; 230 | }); 231 | -------------------------------------------------------------------------------- /assets/modularity-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobbieTheWagner/angular-styleguide-es6/98ba87dfc8b97858ca5c55e83b9c5e6c123af610/assets/modularity-1.png -------------------------------------------------------------------------------- /assets/modularity-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobbieTheWagner/angular-styleguide-es6/98ba87dfc8b97858ca5c55e83b9c5e6c123af610/assets/modularity-2.png -------------------------------------------------------------------------------- /assets/ng-clean-code-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobbieTheWagner/angular-styleguide-es6/98ba87dfc8b97858ca5c55e83b9c5e6c123af610/assets/ng-clean-code-banner.png -------------------------------------------------------------------------------- /assets/sublime-angular-snippets/angular.controller.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 25 | ngcontroller 26 | text.plain, source.js 27 | 28 | -------------------------------------------------------------------------------- /assets/sublime-angular-snippets/angular.directive.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 38 | ngdirective 39 | text.plain, source.js 40 | 41 | -------------------------------------------------------------------------------- /assets/sublime-angular-snippets/angular.factory.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 25 | ngfactory 26 | text.plain, source.js 27 | 28 | -------------------------------------------------------------------------------- /assets/sublime-angular-snippets/angular.filter.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 21 | ngfilter 22 | text.plain, source.js 23 | 24 | -------------------------------------------------------------------------------- /assets/sublime-angular-snippets/angular.module.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 11 | ngmodule 12 | text.plain, source.js 13 | 14 | -------------------------------------------------------------------------------- /assets/sublime-angular-snippets/angular.service.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 22 | ngservice 23 | text.plain, source.js 24 | 25 | -------------------------------------------------------------------------------- /assets/testing-tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobbieTheWagner/angular-styleguide-es6/98ba87dfc8b97858ca5c55e83b9c5e6c123af610/assets/testing-tools.png -------------------------------------------------------------------------------- /assets/vim-angular-snippets/angular.controller.snip: -------------------------------------------------------------------------------- 1 | snippet ngcontroller 2 | options head 3 | (function() { 4 | 'use strict'; 5 | 6 | angular 7 | .module('${1:module}') 8 | .controller('${2:Controller}Controller', $2Controller); 9 | 10 | /* @ngInject */ 11 | function $2Controller(${3:dependencies}) { 12 | var vm = this; 13 | vm.title = '$2Controller'; 14 | 15 | activate(); 16 | 17 | //////////////// 18 | 19 | function activate() { 20 | } 21 | } 22 | })(); 23 | -------------------------------------------------------------------------------- /assets/vim-angular-snippets/angular.directive.snip: -------------------------------------------------------------------------------- 1 | snippet ngdirective 2 | options head 3 | (function() { 4 | 'use strict'; 5 | 6 | angular 7 | .module('${1:module}') 8 | .directive('${2:directive}', $2); 9 | 10 | /* @ngInject */ 11 | function $2(${3:dependencies}) { 12 | // Usage: 13 | // 14 | // Creates: 15 | // 16 | var directive = { 17 | bindToController: true, 18 | controller: ${4:Controller}, 19 | controllerAs: '${5:vm}', 20 | link: link, 21 | restrict: 'A', 22 | scope: { 23 | } 24 | }; 25 | return directive; 26 | 27 | function link(scope, element, attrs) { 28 | } 29 | } 30 | 31 | /* @ngInject */ 32 | function $4() { 33 | 34 | } 35 | })(); 36 | -------------------------------------------------------------------------------- /assets/vim-angular-snippets/angular.factory.snip: -------------------------------------------------------------------------------- 1 | snippet ngfactory 2 | options head 3 | (function() { 4 | 'use strict'; 5 | 6 | angular 7 | .module('${1:module}') 8 | .factory('${2:factory}', $2); 9 | 10 | /* @ngInject */ 11 | function $2(${3:dependencies}) { 12 | var service = { 13 | ${4:func}: $4 14 | }; 15 | return service; 16 | 17 | //////////////// 18 | 19 | function $4() { 20 | } 21 | } 22 | })(); 23 | -------------------------------------------------------------------------------- /assets/vim-angular-snippets/angular.filter.snip: -------------------------------------------------------------------------------- 1 | snippet ngfilter 2 | options head 3 | (function() { 4 | 'use strict'; 5 | 6 | angular 7 | .module('${1:module}') 8 | .filter('${2:filter}', $2); 9 | 10 | function $2() { 11 | return $2Filter; 12 | 13 | //////////////// 14 | function $2Filter(${3:params}) { 15 | return $3; 16 | }; 17 | } 18 | 19 | })(); 20 | -------------------------------------------------------------------------------- /assets/vim-angular-snippets/angular.module.snip: -------------------------------------------------------------------------------- 1 | snippet ngmodule 2 | options head 3 | (function() { 4 | 'use strict'; 5 | 6 | angular 7 | .module('${1:module}', [ 8 | '${2:dependencies}' 9 | ]); 10 | })(); 11 | -------------------------------------------------------------------------------- /assets/vim-angular-snippets/angular.service.snip: -------------------------------------------------------------------------------- 1 | snippet ngservice 2 | options head 3 | (function() { 4 | 'use strict'; 5 | 6 | angular 7 | .module('${1:module}') 8 | .service('${2:Service}', $2); 9 | 10 | /* @ngInject */ 11 | function $2(${3:dependencies}) { 12 | this.${4:func} = $4; 13 | 14 | //////////////// 15 | 16 | function $4() { 17 | } 18 | } 19 | })(); 20 | -------------------------------------------------------------------------------- /assets/vim-angular-ultisnips/javascript_angular.controller.snippets: -------------------------------------------------------------------------------- 1 | snippet ngcontroller 2 | (function() { 3 | 'use strict'; 4 | 5 | angular 6 | .module('${1:module}') 7 | .controller('${2:Controller}Controller', $2Controller); 8 | 9 | /* @ngInject */ 10 | function $2Controller(${3:dependencies}) { 11 | var vm = this; 12 | vm.title = '$2Controller'; 13 | 14 | activate(); 15 | 16 | //////////////// 17 | 18 | function activate() { 19 | } 20 | } 21 | })(); 22 | endsnippet 23 | -------------------------------------------------------------------------------- /assets/vim-angular-ultisnips/javascript_angular.directive.snippets: -------------------------------------------------------------------------------- 1 | snippet ngdirective 2 | (function() { 3 | 'use strict'; 4 | 5 | angular 6 | .module('${1:module}') 7 | .directive('${2:directive}', $2); 8 | 9 | /* @ngInject */ 10 | function $2(${3:dependencies}) { 11 | // Usage: 12 | // 13 | // Creates: 14 | // 15 | var directive = { 16 | bindToController: true, 17 | controller: ${4:Controller}, 18 | controllerAs: '${5:vm}', 19 | link: link, 20 | restrict: 'A', 21 | scope: { 22 | } 23 | }; 24 | return directive; 25 | 26 | function link(scope, element, attrs) { 27 | } 28 | } 29 | 30 | /* @ngInject */ 31 | function $4() { 32 | 33 | } 34 | })(); 35 | endsnippet 36 | -------------------------------------------------------------------------------- /assets/vim-angular-ultisnips/javascript_angular.factory.snippets: -------------------------------------------------------------------------------- 1 | snippet ngfactory 2 | (function() { 3 | 'use strict'; 4 | 5 | angular 6 | .module('${1:module}') 7 | .factory('${2:factory}', $2); 8 | 9 | /* @ngInject */ 10 | function $2(${3:dependencies}) { 11 | var service = { 12 | ${4:func}: $4 13 | }; 14 | return service; 15 | 16 | //////////////// 17 | 18 | function $4() { 19 | } 20 | } 21 | })(); 22 | endsnippet 23 | -------------------------------------------------------------------------------- /assets/vim-angular-ultisnips/javascript_angular.filter.snippets: -------------------------------------------------------------------------------- 1 | snippet ngfilter 2 | (function() { 3 | 'use strict'; 4 | 5 | angular 6 | .module('${1:module}') 7 | .filter('${2:filter}', $2); 8 | 9 | function $2() { 10 | return $2Filter; 11 | 12 | //////////////// 13 | function $2Filter(${3:params}) { 14 | return $3; 15 | }; 16 | } 17 | 18 | })(); 19 | endsnippet 20 | -------------------------------------------------------------------------------- /assets/vim-angular-ultisnips/javascript_angular.module.snippets: -------------------------------------------------------------------------------- 1 | snippet ngmodule 2 | (function() { 3 | 'use strict'; 4 | 5 | angular 6 | .module('${1:module}', [ 7 | '${2:dependencies}' 8 | ]); 9 | })(); 10 | endsnippet 11 | -------------------------------------------------------------------------------- /assets/vim-angular-ultisnips/javascript_angular.service.snippets: -------------------------------------------------------------------------------- 1 | snippet ngservice 2 | (function() { 3 | 'use strict'; 4 | 5 | angular 6 | .module('${1:module}') 7 | .service('${2:Service}', $2); 8 | 9 | /* @ngInject */ 10 | function $2(${3:dependencies}) { 11 | this.${4:func} = $4; 12 | 13 | //////////////// 14 | 15 | function $4() { 16 | } 17 | } 18 | })(); 19 | endsnippet 20 | -------------------------------------------------------------------------------- /assets/vscode-snippets/javascript.json: -------------------------------------------------------------------------------- 1 | { 2 | "Angular Controller": { 3 | "prefix": "ngcontroller", 4 | "body": [ 5 | "(function() {", 6 | "'use strict';", 7 | "", 8 | "\tangular", 9 | "\t\t.module('${Module}')", 10 | "\t\t.controller('${Controller}Controller', ${Controller}Controller);", 11 | "", 12 | "\t${Controller}Controller.$inject = ['${dependency1}'];", 13 | "\tfunction ${Controller}Controller(${dependency1}) {", 14 | "\t\tvar vm = this;", 15 | "\t\t$0", 16 | "", 17 | "\t\tactivate();", 18 | "", 19 | "\t\t////////////////", 20 | "", 21 | "\t\tfunction activate() { }", 22 | "\t}", 23 | "})();" 24 | ], 25 | "description": "Angular 1 controller" 26 | }, 27 | "Angular Service": { 28 | "prefix": "ngservice", 29 | "body": [ 30 | "(function() {", 31 | "'use strict';", 32 | "", 33 | "\tangular", 34 | "\t\t.module('${Module}')", 35 | "\t\t.service('${Service}', ${Service});", 36 | "", 37 | "\t${Service}.$inject = ['${dependency1}'];", 38 | "\tfunction ${Service}(${dependency1}) {", 39 | "\t\tthis.${exposedFn} = ${exposedFn};", 40 | "\t\t$0", 41 | "\t\t////////////////", 42 | "\t\tfunction ${exposedFn}() { }", 43 | "\t}", 44 | "})();" 45 | ], 46 | "description": "Angular 1 service" 47 | }, 48 | "Angular Factory": { 49 | "prefix": "ngfactory", 50 | "body": [ 51 | "(function() {", 52 | "'use strict';", 53 | "", 54 | "\tangular", 55 | "\t\t.module('${Module}')", 56 | "\t\t.factory('${Service}', ${Service});", 57 | "", 58 | "\t${Service}.$inject = ['${dependency1}'];", 59 | "\tfunction ${Service}(${dependency1}) {", 60 | "\t\tvar service = {", 61 | "\t\t\t${exposedFn}:${exposedFn}", 62 | "\t\t};", 63 | "\t\t$0", 64 | "\t\treturn service;", 65 | "", 66 | "\t\t////////////////", 67 | "\t\tfunction ${exposedFn}() { }", 68 | "\t}", 69 | "})();" 70 | ], 71 | "description": "Angular 1 factory" 72 | }, 73 | "Angular Directive": { 74 | "prefix": "ngdirective", 75 | "body": [ 76 | "(function() {", 77 | "\t'use strict';", 78 | "", 79 | "\tangular", 80 | "\t\t.module('${Module}')", 81 | "\t\t.directive('${Directive}', ${Directive});", 82 | "", 83 | "\t${Directive}.$inject = ['${dependency1}'];", 84 | "\tfunction ${Directive}(${dependency1}) {", 85 | "\t\t// Usage:", 86 | "\t\t//", 87 | "\t\t// Creates:", 88 | "\t\t//", 89 | "\t\tvar directive = {", 90 | "\t\t bindToController: true,", 91 | "\t\t controller: ${Controller}Controller,", 92 | "\t\t controllerAs: '${vm}',", 93 | "\t\t link: link,", 94 | "\t\t restrict: 'A',", 95 | "\t\t scope: {", 96 | "\t\t }", 97 | "\t\t};", 98 | "\t\treturn directive;", 99 | "\t\t", 100 | "\t\tfunction link(scope, element, attrs) {", 101 | "\t\t}", 102 | "\t}", 103 | "\t/* @ngInject */", 104 | "\tfunction ${Controller}Controller () {", 105 | "\t\t$0", 106 | "\t}", 107 | "})();" 108 | ], 109 | "description": "Angular 1 directive" 110 | }, 111 | "Angular Module": { 112 | "prefix": "ngmodule", 113 | "body": [ 114 | "(function() {", 115 | "\t'use strict';", 116 | "", 117 | "\tangular.module('${module}', [", 118 | "\t\t$0", 119 | "\t]);", 120 | "})();" 121 | ], 122 | "description": "Angular 1 module" 123 | } 124 | } -------------------------------------------------------------------------------- /assets/vscode-snippets/typescript.json: -------------------------------------------------------------------------------- 1 | { 2 | "Angular Controller": { 3 | "prefix": "ngcontroller", 4 | "body": [ 5 | "namespace ${module} {", 6 | "'use strict';", 7 | "", 8 | "export class ${Controller}Controller {", 9 | "\tstatic $inject: Array = ['${dependency1}'];", 10 | "\tconstructor(private ${dependency1}: ${dependency1Type}) {", 11 | "$0", 12 | "}", 13 | "", 14 | "\t${property}: ${propertyType} = ${propertyValue};", 15 | "", 16 | "\t${fn}() { }", 17 | "}", 18 | "", 19 | "angular", 20 | "\t.module('${Module}')", 21 | "\t.controller('${Controller}Controller', ${Controller}Controller);", 22 | "}" 23 | ] 24 | }, 25 | "Angular Service": { 26 | "prefix": "ngservice", 27 | "body": [ 28 | "namespace ${module} {", 29 | "'use strict';", 30 | "", 31 | "export interface I${Service} {", 32 | "\t${serviceFn}: (${dependency1}:${dependency1Type}) => ${returnType};", 33 | "}", 34 | "export class ${Service} implements I${Service} {", 35 | "\tstatic $inject: Array = ['${dependency1}'];", 36 | "\tconstructor(private ${dependency1}: ${dependency1Type}) {", 37 | "", 38 | "}", 39 | "", 40 | "\t${serviceFn}: (${dependency1}:${dependency1Type}) => ${returnType} = (${dependency1}:${dependency1Type}) => {", 41 | "\t\t$0", 42 | "\t}", 43 | "", 44 | "}", 45 | "", 46 | "angular", 47 | "\t.module('${Module}')", 48 | "\t.service('${Service}', ${Service});", 49 | "}" 50 | ] 51 | }, 52 | "Angular Module": { 53 | "prefix": "ngmodule", 54 | "body": [ 55 | "namespace ${module} {", 56 | "\t'use strict';", 57 | "", 58 | "\tangular.module('${module}', [", 59 | "\t$0", 60 | "\t]);", 61 | "}" 62 | ] 63 | } 64 | } -------------------------------------------------------------------------------- /assets/webstorm-angular-file-template.settings.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobbieTheWagner/angular-styleguide-es6/98ba87dfc8b97858ca5c55e83b9c5e6c123af610/assets/webstorm-angular-file-template.settings.jar -------------------------------------------------------------------------------- /i18n/README.md: -------------------------------------------------------------------------------- 1 | #Translations 2 | 3 | The [original English version](http://jpapa.me/ngstyles) is the source of truth, as it is maintained and updated first. 4 | 5 | *All translations are created by and maintained by the community.* 6 | 7 | 1. [French](fr-FR.md) by [Eric Le Merdy](https://github.com/ericlemerdy) and [Xavier Haniquaut] (@xavhan) 8 | 1. [German](de-DE.md) by [Michael Seeger](https://github.com/miseeger), [Sascha Hagedorn](https://github.com/saesh) and [Johannes Weber](https://github.com/johannes-weber) 9 | 1. [Italian](it-IT.md) by [Angelo Chiello](https://github.com/angelochiello) 10 | 1. [Japanese](ja-JP.md) by [@noritamago](https://github.com/noritamago) 11 | 1. [Macedonian](mk-MK.md) by [Aleksandar Bogatinov](https://github.com/Bogatinov) 12 | 1. [Portuguese-Brazil](PT-BR.md) by [Vinicius Sabadim Fernandes](https://github.com/vinicius-sabadim) 13 | 1. [Russian](ru-RU.md) by [Vasiliy Mazhekin](https://github.com/mazhekin) 14 | 1. [Simplified Chinese](zh-CN.md) by [Zhao Ke](https://github.com/natee) 15 | 1. [Spanish](es-ES.md) by [Alberto Calleja](https://github.com/AlbertoImpl) and [Gilberto](https://github.com/ingilniero) 16 | 17 | 18 | ## Contributing 19 | Language translations are welcomed and encouraged. The success of these translations depends on the community. I highly encourage new translation contributions and help to keep them up to date. 20 | 21 | All translations must preserve the intention of the original document. 22 | 23 | > All contributions fall under the [MIT License of this repository](https://github.com/johnpapa/angularjs-styleguide#license). In other words, you would be providing these free to the community. 24 | 25 | ### New Translations 26 | 1. Fork the repository 27 | 2. Create a translation file and name it using the 118n standard format. 28 | 3. Put this file in the i18n folder 29 | 4. Translate the original English version to be current with the latest changes 30 | 3. Make a Pull Request 31 | 32 | Once you do these I will merge, point the translation links to it, and enter the translation credit to you. 33 | 34 | ### Updated Translations 35 | 1. Fork the repository 36 | 2. Make the translation changes 37 | 3. Make a Pull Request 38 | 39 | Once you do these I will merge, point the translation links to it, and enter the translation credit to you. 40 | 41 | --------------------------------------------------------------------------------