|
1 |
| -angularjs-styleguide |
2 |
| -==================== |
| 1 | +# AngularJS Style Guide |
3 | 2 |
|
4 |
| -angularjs-styleguide |
| 3 | +*Opinionated AngularJS style guide for teams by [@john_papa](//twitter.com/john_papa)* |
| 4 | + |
| 5 | +Looking for an opinionated style guide for syntax, conventions, and structuring AngularJS applications. Then step right in. The styles contained here are based on on my experience with [AngularJS](//angularjs.org), presentations, Pluralsight training courses and working in teams. |
| 6 | + |
| 7 | +The purpose of this style guide is to provide guidance on building AngularJS applications by showing the conventions I use and , more importantly, why I choose them. |
| 8 | + |
| 9 | + |
| 10 | +## Community Awesomeness |
| 11 | +Never work in a vacuum. I find that the AngularJS community is an incredible group who are passionate about sharing experiences. As such, a friend and AngularJS expert Todd Motto and I have collaborated on many styles and conventions. We agree on most, and some we diverge. I encourage you to check out [Todd's guidelines](https://github.com/toddmotto/angularjs-styleguide) to get a sense for his approach and how it compares. |
| 12 | + |
| 13 | + |
| 14 | +## Table of Contents |
| 15 | + |
| 16 | + 1. [Separations of Concerns](#separation-of-concerns) |
| 17 | + 1. [Modules](#modules) |
| 18 | + 1. [Controllers](#controllers) |
| 19 | + |
| 20 | +## Separation of Concerns |
| 21 | + |
| 22 | + - **Rule of 1**: Define 1 component per file. |
| 23 | + |
| 24 | + The following example defines the `app` module and its dependencies, defines a controller, and defines a factory all in the same file. |
| 25 | + |
| 26 | + ```javascript |
| 27 | + /* avoid */ |
| 28 | + angular |
| 29 | + .module('app', ['ngRoute']) |
| 30 | + .controller('SomeController' , SomeController) |
| 31 | + .factory('someFactory' , someFactory); |
| 32 | + |
| 33 | + function SomeController() { } |
| 34 | + |
| 35 | + function someFactory() { } |
| 36 | + ``` |
| 37 | + |
| 38 | + The same components are now separated into their own files. |
| 39 | + |
| 40 | + ```javascript |
| 41 | + /* recommended */ |
| 42 | + |
| 43 | + // app.module.js |
| 44 | + angular |
| 45 | + .module('app', ['ngRoute']); |
| 46 | + ``` |
| 47 | + |
| 48 | + ```javascript |
| 49 | + /* recommended */ |
| 50 | + |
| 51 | + // someController.js |
| 52 | + angular |
| 53 | + .module('app') |
| 54 | + .controller('SomeController' , SomeController); |
| 55 | + |
| 56 | + function SomeController() { } |
| 57 | + ``` |
| 58 | + |
| 59 | + ```javascript |
| 60 | + /* recommended */ |
| 61 | + |
| 62 | + // someFactory.js |
| 63 | + angular |
| 64 | + .module('app') |
| 65 | + .factory('someFactory' , someFactory); |
| 66 | + |
| 67 | + function someFactory() { } |
| 68 | + ``` |
| 69 | + |
| 70 | +**[Back to top](#table-of-contents)** |
| 71 | + |
| 72 | +## Modules |
| 73 | + |
| 74 | + - **Definitions (aka Setters)**: Declare modules without a variable using the setter syntax. |
| 75 | + |
| 76 | + *Why?*: With 1 component per file, there is rarely a need to introduce a variable for the module. |
| 77 | + |
| 78 | + ```javascript |
| 79 | + /* avoid */ |
| 80 | + var app = angular.module('app', [ |
| 81 | + 'ngAnimate', |
| 82 | + 'ngRoute', |
| 83 | + 'app.shared' |
| 84 | + 'app.dashboard' |
| 85 | + ]); |
| 86 | + ``` |
| 87 | + |
| 88 | + Instead use the simple getter syntax. |
| 89 | + |
| 90 | + ```javascript |
| 91 | + /* recommended */ |
| 92 | + angular |
| 93 | + .module('app', [ |
| 94 | + 'ngAnimate', |
| 95 | + 'ngRoute', |
| 96 | + 'app.shared' |
| 97 | + 'app.dashboard' |
| 98 | + ]); |
| 99 | + ``` |
| 100 | + |
| 101 | + - **Getters**: When using a module, avoid using a variables and instead use chaining with the getter syntax. |
| 102 | + |
| 103 | + *Why?* : This produces more readable code and avoids variables collisions or leaks. |
| 104 | + |
| 105 | + ```javascript |
| 106 | + /* avoid */ |
| 107 | + var app = angular.module('app'); |
| 108 | + app.controller('SomeController' , SomeController); |
| 109 | + |
| 110 | + function SomeController() { } |
| 111 | + ``` |
| 112 | + |
| 113 | + ```javascript |
| 114 | + /* recommended */ |
| 115 | + angular |
| 116 | + .module('app') |
| 117 | + .controller('SomeController' , SomeController); |
| 118 | + |
| 119 | + function SomeController() { } |
| 120 | + ``` |
| 121 | + |
| 122 | + - **Setting vs Getting**: Only set once and get for all other instances. |
| 123 | + |
| 124 | + *Why?*: A module should only be created once, then retrieved from that point and after. |
| 125 | + |
| 126 | + - Use `angular.module('app', []);` to set a module. |
| 127 | + - Use `angular.module('app');` to get a module. |
| 128 | + |
| 129 | + - **Named vs Anonymous Functions**: Use named functions instead of passing an anonymous function in as a callback. |
| 130 | + |
| 131 | + *Why?*: This produces more readable code, is much easier to debug, and reduces the amount of nested callback code. |
| 132 | + |
| 133 | + ```javascript |
| 134 | + /* avoid */ |
| 135 | + angular |
| 136 | + .module('app') |
| 137 | + .controller('Dashboard', function () { }); |
| 138 | + .factory('logger', function () { }); |
| 139 | + ``` |
| 140 | + |
| 141 | + ```javascript |
| 142 | + /* recommended */ |
| 143 | + |
| 144 | + // dashboard.js |
| 145 | + angular |
| 146 | + .module('app') |
| 147 | + .controller('Dashboard', Dashboard); |
| 148 | + |
| 149 | + function Dashboard () { } |
| 150 | + ``` |
| 151 | + |
| 152 | + ```javascript |
| 153 | + // logger.js |
| 154 | + angular |
| 155 | + .module('app') |
| 156 | + .factory('logger', logger); |
| 157 | + |
| 158 | + function logger () { } |
| 159 | + ``` |
| 160 | + |
| 161 | + - **IIFE**: Wrap AngularJS components in an Immediately Invoked Function Expression (IIFE). |
| 162 | + |
| 163 | + *Why?*: An IIFE removes variables from the global scope. This helps prevent variables and function declarations from living longer than expected in the global scope, which also helps avoid variable collisions. |
| 164 | + |
| 165 | + ```javascript |
| 166 | + (function () { |
| 167 | + angular |
| 168 | + .module('app') |
| 169 | + .factory('logger', logger); |
| 170 | + |
| 171 | + function logger () { } |
| 172 | + })(); |
| 173 | + ``` |
| 174 | + |
| 175 | + - Note: For brevity only, the rest of the examples in this guide may omit the IIFE syntax. |
| 176 | + |
| 177 | +**[Back to top](#table-of-contents)** |
| 178 | + |
| 179 | +## Controllers |
| 180 | + |
| 181 | + - **controllerAs View Syntax**: Use the `controllerAs` syntax over the `classic controller with $scope` syntax. |
| 182 | + |
| 183 | + *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`. |
| 184 | + |
| 185 | + *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". |
| 186 | + |
| 187 | + *Why?*: Helps avoid using `$parent` calls in Views with nested controllers. |
| 188 | + |
| 189 | + *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". |
| 190 | + |
| 191 | + ```html |
| 192 | + <!-- avoid --> |
| 193 | + <div ng-controller="Customer"> |
| 194 | + {{ name }} |
| 195 | + </div> |
| 196 | + ``` |
| 197 | + |
| 198 | + ```html |
| 199 | + <!-- recommended --> |
| 200 | + <div ng-controller="Customer as customer"> |
| 201 | + {{ customer.name }} |
| 202 | + </div> |
| 203 | + ``` |
| 204 | + |
| 205 | + - **controllerAs Controller Syntax**: Use the `controllerAs` syntax over the `classic controller with $scope` syntax. |
| 206 | + |
| 207 | + - The `controllerAs` syntax uses `this` inside controllers which gets bound to `$scope` |
| 208 | + |
| 209 | + *Why?*: `controllerAs` is syntactic sugar over `$scope`. You can still bind to the View and still access `$scope` methods. |
| 210 | + |
| 211 | + *Why?*: Helps avoid the tempation of using `$scope` methods inside a controller when it may otherwise be better to avoid them or move them to a factory. Consider using `$scope` in a factory, or if in a controller just when needed. For example when publishing and subscribing events using `$emit`, `$broadcast`, or `$on` consider moving these uses to a factory and invoke form the controller. |
| 212 | + |
| 213 | + ```javascript |
| 214 | + /* avoid */ |
| 215 | + function Customer ($scope) { |
| 216 | + $scope.name = {}; |
| 217 | + $scope.sendMessage = function () { }; |
| 218 | + } |
| 219 | + ``` |
| 220 | + |
| 221 | + ```javascript |
| 222 | + /* recommended - but see next section */ |
| 223 | + function Customer () { |
| 224 | + this.name = {}; |
| 225 | + this.sendMessage = function () { }; |
| 226 | + } |
| 227 | + ``` |
| 228 | + |
| 229 | + - **controllerAs with vm**: Use a capture variable for `this` when using the `controllerAs` syntax. |
| 230 | + |
| 231 | + *Why?*: The `this` keyword is contextual and when used within a function inside a controller may change its context. Capturing the context of `this` avoids encountering this problem. |
| 232 | + |
| 233 | + ```javascript |
| 234 | + /* avoid */ |
| 235 | + function Customer () { |
| 236 | + this.name = {}; |
| 237 | + this.sendMessage = function () { }; |
| 238 | + } |
| 239 | + ``` |
| 240 | + |
| 241 | + ```javascript |
| 242 | + /* recommended */ |
| 243 | + function Customer () { |
| 244 | + var vm = this; |
| 245 | + vm.name = {}; |
| 246 | + vm.sendMessage = function () { }; |
| 247 | + } |
| 248 | + ``` |
| 249 | + |
| 250 | + - Note: You can avoid any jshint warnings by placing the comment below above the line of code. |
| 251 | + |
| 252 | + ```javascript |
| 253 | + /* jshint validthis: true */ |
| 254 | + var vm = this; |
| 255 | + ``` |
| 256 | + |
| 257 | + - **Bindable Members Up Top**: Place bindable members at the top of the controller and not spread through the controller code. |
| 258 | + |
| 259 | + *Why?*: Placing bindable members at the top makes it easy to read and helps you instantly identify which members of the controller can be bound and used in the View. |
| 260 | + |
| 261 | + *Why?*: Setting anonymous functions inline can be easy, but when those functions are more than 1 line of code they can reduce the readability. Defining the functions below the bindable members (the functions will be hoisted) moves the implementation details down, keeps the bindable members up top, and makes it easier to read. |
| 262 | + |
| 263 | + ```javascript |
| 264 | + /* avoid */ |
| 265 | + function Sessions() { |
| 266 | + var vm = this; |
| 267 | +
|
| 268 | + vm.gotoSession = function() { |
| 269 | + /* ... */ |
| 270 | + }; |
| 271 | + vm.refresh = function() { |
| 272 | + /* ... */ |
| 273 | + }; |
| 274 | + vm.search = function() { |
| 275 | + /* ... */ |
| 276 | + }; |
| 277 | + vm.sessions = []; |
| 278 | + vm.title = 'Sessions'; |
| 279 | + ``` |
| 280 | + |
| 281 | + ```javascript |
| 282 | + /* recommended */ |
| 283 | + function Sessions() { |
| 284 | + var vm = this; |
| 285 | +
|
| 286 | + vm.gotoSession = gotoSession; |
| 287 | + vm.refresh = refresh; |
| 288 | + vm.search = search; |
| 289 | + vm.sessions = []; |
| 290 | + vm.title = 'Sessions'; |
| 291 | +
|
| 292 | + //////////// |
| 293 | +
|
| 294 | + function gotoSession() { |
| 295 | + /* */ |
| 296 | + } |
| 297 | +
|
| 298 | + function refresh() { |
| 299 | + /* */ |
| 300 | + } |
| 301 | +
|
| 302 | + function search() { |
| 303 | + /* */ |
| 304 | + } |
| 305 | + ``` |
| 306 | + |
| 307 | + - **Defer Controller Logic**: Defer logic in a controller by delegating to services and factories. |
| 308 | + |
| 309 | + *Why?*: Logic may be reused by multiple controllers when placed within a service and exposed via a function. |
| 310 | + |
| 311 | + ```javascript |
| 312 | + /* avoid */ |
| 313 | + function Customer ($scope) { |
| 314 | + var vm = this; |
| 315 | + vm.name = {}; |
| 316 | + vm.sendMessage = sendMessage; |
| 317 | + |
| 318 | + function sendMessage () { |
| 319 | + var msg = 'some message'; |
| 320 | + $scope.$broadcast( /* */); |
| 321 | + }; |
| 322 | + } |
| 323 | + ``` |
| 324 | + |
| 325 | + ```javascript |
| 326 | + /* recommended */ |
| 327 | + function Customer (messager) { |
| 328 | + var vm = this; |
| 329 | + vm.name = {}; |
| 330 | + vm.sendMessage = sendMessage; |
| 331 | +
|
| 332 | + function sendMessage () { |
| 333 | + var msg = 'some message'; |
| 334 | + messager.send(msg); |
| 335 | + }; |
| 336 | + } |
| 337 | + ``` |
| 338 | + |
| 339 | +**[Back to top](#table-of-contents)** |
| 340 | + |
| 341 | +## AngularJS docs |
| 342 | +For anything else, API reference, check the [Angular documentation](//docs.angularjs.org/api). |
| 343 | + |
| 344 | +## Contributing |
| 345 | + |
| 346 | +Open an issue first to discuss potential changes/additions. |
| 347 | + |
| 348 | +## License |
| 349 | + |
| 350 | +#### (The MIT License) |
| 351 | + |
| 352 | +Copyright (c) 2014 John Papa |
| 353 | + |
| 354 | +Permission is hereby granted, free of charge, to any person obtaining |
| 355 | +a copy of this software and associated documentation files (the |
| 356 | +'Software'), to deal in the Software without restriction, including |
| 357 | +without limitation the rights to use, copy, modify, merge, publish, |
| 358 | +distribute, sublicense, and/or sell copies of the Software, and to |
| 359 | +permit persons to whom the Software is furnished to do so, subject to |
| 360 | +the following conditions: |
| 361 | + |
| 362 | +The above copyright notice and this permission notice shall be |
| 363 | +included in all copies or substantial portions of the Software. |
| 364 | + |
| 365 | +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, |
| 366 | +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| 367 | +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
| 368 | +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
| 369 | +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
| 370 | +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
| 371 | +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
0 commit comments