├── LICENSE ├── README.md ├── a1 ├── README.md ├── assets │ ├── above-the-fold-1.png │ ├── above-the-fold-2.png │ ├── brackets-angular-snippets.yaml │ ├── emacs-angular-snippets │ │ ├── javascript-mode │ │ │ └── .yas-parents │ │ ├── js-mode │ │ │ └── .yas-parents │ │ ├── js2-mode │ │ │ ├── angular.controller.snip │ │ │ ├── angular.directive.snip │ │ │ ├── angular.factory.snip │ │ │ ├── angular.filter.snip │ │ │ ├── angular.module.snip │ │ │ └── angular.service.snip │ │ └── js3-mode │ │ │ └── .yas-parents │ ├── gde.png │ ├── modularity-1.png │ ├── modularity-2.png │ ├── ng-clean-code-banner.png │ ├── sublime-angular-snippets │ │ ├── angular.component.sublime-snippet │ │ ├── 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-live-templates │ │ ├── angular.app.webstorm-live-template.xml │ │ ├── angular.component.webstorm-live-template.xml │ │ ├── angular.config.webstorm-live-template.xml │ │ ├── angular.controller.webstorm-live-template.xml │ │ ├── angular.directive.webstorm-live-template.xml │ │ ├── angular.factory.webstorm-live-template.xml │ │ ├── angular.filter.webstorm-live-template.xml │ │ ├── angular.module.webstorm-live-template.xml │ │ ├── angular.route.webstorm-live-template.xml │ │ ├── angular.run.webstorm-live-template.xml │ │ ├── angular.service.webstorm-live-template.xml │ │ ├── angular.state.webstorm-live-template.xml │ │ └── webstorm-angular-live-templates.xml └── i18n │ ├── README.md │ ├── de-DE.md │ ├── es-ES.md │ ├── fr-FR.md │ ├── it-IT.md │ ├── ja-JP.md │ ├── ko-KR.md │ ├── mk-MK.md │ ├── pt-BR.md │ ├── ro-RO.md │ ├── ru-RU.md │ ├── tr-TR.md │ └── zh-CN.md └── a2 ├── README.md └── notes.md /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2016 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 2 | 3 | ## Versions 4 | There are multiple versions of Angular, and thus there are multiple versions of the guide. Choose your guide appropriately. 5 | 6 | ### Angular 1 Style Guide 7 | [The Angular 1 Style Guide](https://github.com/johnpapa/angular-styleguide/tree/master/a1/README.md). 8 | 9 | ### Angular 2 Style Guide 10 | [The Angular 2 Style Guide](https://github.com/johnpapa/angular-styleguide/tree/master/a2/README.md). 11 | 12 | ## Angular Team Endorsed 13 | Special thanks to Igor Minar, lead on the Angular team, for reviewing, contributing feedback, and entrusting me to shepherd this guide. 14 | 15 | ## Purpose 16 | *Opinionated Angular style guide for teams by [@john_papa](//twitter.com/john_papa)* 17 | 18 | If you are looking for an opinionated style guide for syntax, conventions, and structuring Angular applications, then step right in. These styles are based on my development experience with [Angular](//angularjs.org), presentations, [Pluralsight training courses](http://app.pluralsight.com/author/john-papa) and working in teams. 19 | 20 | The purpose of this style guide is to provide guidance on building Angular applications by showing the conventions I use and, more importantly, why I choose them. 21 | 22 | >If you like this guide, check out my [Angular Patterns: Clean Code](http://jpapa.me/ngclean) course at Pluralsight which is a companion to this guide. 23 | 24 | [![Angular Patterns: Clean Code](https://raw.githubusercontent.com/johnpapa/angular-styleguide/master/a1/assets/ng-clean-code-banner.png)](http://jpapa.me/ngclean) 25 | 26 | ## Community Awesomeness and Credit 27 | Never work in a vacuum. I find that the Angular community is an incredible group who are passionate about sharing experiences. Many of my styles have been from the many pair programming sessions [Ward Bell](https://twitter.com/wardbell) and I have had. My most excellent friend Ward has helped influence the ultimate evolution of these guides. 28 | 29 | ## Contributing 30 | 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. 31 | 32 | *By contributing to this repository you are agreeing to make your content available subject to the license of this repository.* 33 | 34 | ### Process 35 | 1. Discuss the changes in a GitHub issue. 36 | 2. Open a Pull Request, reference the issue, and explain the change and why it adds value. 37 | 3. The Pull Request will be evaluated and either merged or declined. 38 | 39 | ## License 40 | 41 | _tldr; Use this guide. Attributions are appreciated._ 42 | 43 | ### Copyright 44 | 45 | Copyright (c) 2014-2016 [John Papa](http://johnpapa.net) 46 | 47 | ### (The MIT License) 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of this software and associated documentation files (the 50 | 'Software'), to deal in the Software without restriction, including 51 | without limitation the rights to use, copy, modify, merge, publish, 52 | distribute, sublicense, and/or sell copies of the Software, and to 53 | permit persons to whom the Software is furnished to do so, subject to 54 | the following conditions: 55 | 56 | The above copyright notice and this permission notice shall be 57 | included in all copies or substantial portions of the Software. 58 | 59 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 60 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 61 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 62 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 63 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 64 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 65 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 66 | 67 | **[Back to top](#angular-style-guide)** 68 | -------------------------------------------------------------------------------- /a1/assets/above-the-fold-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/angular-styleguide/7019503f2c8a637d1a70f5719cb17b9669653565/a1/assets/above-the-fold-1.png -------------------------------------------------------------------------------- /a1/assets/above-the-fold-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/angular-styleguide/7019503f2c8a637d1a70f5719cb17b9669653565/a1/assets/above-the-fold-2.png -------------------------------------------------------------------------------- /a1/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 | var directive = { 106 | bindToController: true, 107 | controller: ${4:Controller}, 108 | controllerAs: '${5:vm}', 109 | link: link, 110 | restrict: 'A', 111 | scope: {} 112 | }; 113 | return directive; 114 | 115 | function link(scope, element, attrs, controller) { 116 | ${7:} 117 | } 118 | } 119 | 120 | ${4:Controller}.$inject = ['${6:dependencies}']; 121 | 122 | /* @ngInject */ 123 | function ${4:Controller}(${6:dependencies}) { 124 | } 125 | })(); 126 | 127 | - trigger: ngcontroller 128 | description: "Controller" 129 | scope: javascript 130 | text: | 131 | (function() { 132 | 'use strict'; 133 | 134 | angular 135 | .module('${1:module}') 136 | .controller('${2:Controller}', ${2:Controller}); 137 | 138 | ${2:Controller}.$inject = ['${3:dependencies}']; 139 | 140 | /* @ngInject */ 141 | function ${2:Controller}(${3:dependencies}){ 142 | var vm = this; 143 | vm.${4:property} = '${2:Controller}'; 144 | ${5:} 145 | 146 | activate(); 147 | 148 | //////////////// 149 | 150 | function activate() { 151 | } 152 | } 153 | })(); 154 | 155 | - trigger: ngwhen 156 | description: "ngRoute 'when'" 157 | scope: javascript 158 | text: | 159 | .when('/${1:url}', { 160 | templateUrl: '${2:template}.html', 161 | controller: '${3:Controller}', 162 | controllerAs: '${4:vm}' 163 | })${5:} 164 | 165 | - trigger: ngstate 166 | description: "UI-Router state" 167 | scope: javascript 168 | text: | 169 | .state('${1:state}', { 170 | url: '${2:/url}', 171 | templateUrl: '${3:template}.html', 172 | controller: '${4:Controller}', 173 | controllerAs: '${5:vm}' 174 | })${6:} 175 | 176 | - trigger: ngmodule 177 | description: "Module getter" 178 | scope: javascript 179 | text: | 180 | angular 181 | .module('${1:module}')${2:} 182 | - trigger: ngconst 183 | description: "Constant" 184 | scope: javascript 185 | text: | 186 | .constant('${1:name}', ${2:value}); 187 | 188 | - trigger: ngvalue 189 | description: "Value" 190 | scope: javascript 191 | text: | 192 | .value('${1:name}', ${2:value}); 193 | 194 | - trigger: ngconfig 195 | description: "Config phase function" 196 | scope: javascript 197 | text: | 198 | .config(${1:configuration}) 199 | 200 | ${1:configuration}.$inject = ['${2:dependencies}']; 201 | 202 | /* @ngInject */ 203 | function ${1:configuration} (${2:dependencies}) { 204 | ${3:} 205 | } 206 | 207 | - trigger: ngrun 208 | description: "Run phase function" 209 | scope: javascript 210 | text: | 211 | .run(${1:runFn}) 212 | 213 | ${1:runFn}.$inject = ['${2:dependencies}']; 214 | 215 | /* @ngInject */ 216 | function ${1:runFn} (${2:dependencies}) { 217 | ${3:} 218 | } 219 | 220 | - trigger: ngtranslate 221 | description: "$translate service" 222 | scope: javascript 223 | text: | 224 | $translate(['${1:key1}']).then(function(translations){ 225 | ${2:value} = translations['${3:key1}']; 226 | }); 227 | -------------------------------------------------------------------------------- /a1/assets/emacs-angular-snippets/javascript-mode/.yas-parents: -------------------------------------------------------------------------------- 1 | js2-mode 2 | -------------------------------------------------------------------------------- /a1/assets/emacs-angular-snippets/js-mode/.yas-parents: -------------------------------------------------------------------------------- 1 | js2-mode 2 | -------------------------------------------------------------------------------- /a1/assets/emacs-angular-snippets/js2-mode/angular.controller.snip: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet; require-final-newline: nil -*- 2 | # name: ngcontroller 3 | # key: ngcontroller 4 | # binding: direct-keybinding 5 | # -- 6 | 7 | (function() { 8 | 'use strict'; 9 | 10 | angular 11 | .module('${1:module}') 12 | .controller('${2:Controller}Controller', $2Controller); 13 | 14 | /* @ngInject */ 15 | function $2Controller(${3:dependencies}) { 16 | var vm = this; 17 | vm.title = '$2Controller'; 18 | 19 | activate(); 20 | 21 | //////////////// 22 | 23 | function activate() { 24 | } 25 | } 26 | })(); 27 | -------------------------------------------------------------------------------- /a1/assets/emacs-angular-snippets/js2-mode/angular.directive.snip: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet; require-final-newline: nil -*- 2 | # name: ngdirective 3 | # key: ngdirective 4 | # binding: direct-keybinding 5 | # -- 6 | 7 | (function() { 8 | 'use strict'; 9 | 10 | angular 11 | .module('${1:module}') 12 | .directive('${2:directive}', $2); 13 | 14 | /* @ngInject */ 15 | function $2(${3:dependencies}) { 16 | // Usage: 17 | // 18 | // Creates: 19 | // 20 | var directive = { 21 | bindToController: true, 22 | controller: ${4:Controller}, 23 | controllerAs: '${5:vm}', 24 | link: link, 25 | restrict: 'A', 26 | scope: { 27 | } 28 | }; 29 | return directive; 30 | 31 | function link(scope, element, attrs) { 32 | } 33 | } 34 | 35 | /* @ngInject */ 36 | function $4() { 37 | 38 | } 39 | })(); 40 | -------------------------------------------------------------------------------- /a1/assets/emacs-angular-snippets/js2-mode/angular.factory.snip: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet; require-final-newline: nil -*- 2 | # name: ngfactory 3 | # key: ngfactory 4 | # binding: direct-keybinding 5 | # -- 6 | 7 | (function() { 8 | 'use strict'; 9 | 10 | angular 11 | .module('${1:module}') 12 | .factory('${2:factory}', $2); 13 | 14 | /* @ngInject */ 15 | function $2(${3:dependencies}) { 16 | var service = { 17 | ${4:func}: $4 18 | }; 19 | return service; 20 | 21 | //////////////// 22 | 23 | function $4() { 24 | } 25 | } 26 | })(); 27 | -------------------------------------------------------------------------------- /a1/assets/emacs-angular-snippets/js2-mode/angular.filter.snip: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet; require-final-newline: nil -*- 2 | # name: ngfilter 3 | # key: ngfilter 4 | # binding: direct-keybinding 5 | # -- 6 | 7 | (function() { 8 | 'use strict'; 9 | 10 | angular 11 | .module('${1:module}') 12 | .filter('${2:filter}', $2); 13 | 14 | function $2() { 15 | return $2Filter; 16 | 17 | //////////////// 18 | function $2Filter(${3:params}) { 19 | return $3; 20 | }; 21 | } 22 | 23 | })(); 24 | -------------------------------------------------------------------------------- /a1/assets/emacs-angular-snippets/js2-mode/angular.module.snip: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet; require-final-newline: nil -*- 2 | # name: ngmodule 3 | # key: ngmodule 4 | # binding: direct-keybinding 5 | # -- 6 | 7 | (function() { 8 | 'use strict'; 9 | 10 | angular 11 | .module('${1:module}', [ 12 | '${2:dependencies}' 13 | ]); 14 | })(); 15 | -------------------------------------------------------------------------------- /a1/assets/emacs-angular-snippets/js2-mode/angular.service.snip: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet; require-final-newline: nil -*- 2 | # name: ngservice 3 | # key: ngservice 4 | # binding: direct-keybinding 5 | # -- 6 | 7 | (function() { 8 | 'use strict'; 9 | 10 | angular 11 | .module('${1:module}') 12 | .service('${2:Service}', $2); 13 | 14 | /* @ngInject */ 15 | function $2(${3:dependencies}) { 16 | this.${4:func} = $4; 17 | 18 | //////////////// 19 | 20 | function $4() { 21 | } 22 | } 23 | })(); 24 | -------------------------------------------------------------------------------- /a1/assets/emacs-angular-snippets/js3-mode/.yas-parents: -------------------------------------------------------------------------------- 1 | js2-mode 2 | -------------------------------------------------------------------------------- /a1/assets/gde.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/angular-styleguide/7019503f2c8a637d1a70f5719cb17b9669653565/a1/assets/gde.png -------------------------------------------------------------------------------- /a1/assets/modularity-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/angular-styleguide/7019503f2c8a637d1a70f5719cb17b9669653565/a1/assets/modularity-1.png -------------------------------------------------------------------------------- /a1/assets/modularity-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/angular-styleguide/7019503f2c8a637d1a70f5719cb17b9669653565/a1/assets/modularity-2.png -------------------------------------------------------------------------------- /a1/assets/ng-clean-code-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/angular-styleguide/7019503f2c8a637d1a70f5719cb17b9669653565/a1/assets/ng-clean-code-banner.png -------------------------------------------------------------------------------- /a1/assets/sublime-angular-snippets/angular.component.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 23 | ngcomponent 24 | text.plain, source.js 25 | 26 | -------------------------------------------------------------------------------- /a1/assets/sublime-angular-snippets/angular.controller.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 25 | ngcontroller 26 | text.plain, source.js 27 | 28 | -------------------------------------------------------------------------------- /a1/assets/sublime-angular-snippets/angular.directive.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 38 | ngdirective 39 | text.plain, source.js 40 | 41 | -------------------------------------------------------------------------------- /a1/assets/sublime-angular-snippets/angular.factory.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 25 | ngfactory 26 | text.plain, source.js 27 | 28 | -------------------------------------------------------------------------------- /a1/assets/sublime-angular-snippets/angular.filter.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 21 | ngfilter 22 | text.plain, source.js 23 | 24 | -------------------------------------------------------------------------------- /a1/assets/sublime-angular-snippets/angular.module.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 11 | ngmodule 12 | text.plain, source.js 13 | 14 | -------------------------------------------------------------------------------- /a1/assets/sublime-angular-snippets/angular.service.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 22 | ngservice 23 | text.plain, source.js 24 | 25 | -------------------------------------------------------------------------------- /a1/assets/testing-tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/angular-styleguide/7019503f2c8a637d1a70f5719cb17b9669653565/a1/assets/testing-tools.png -------------------------------------------------------------------------------- /a1/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 | -------------------------------------------------------------------------------- /a1/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 | -------------------------------------------------------------------------------- /a1/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 | -------------------------------------------------------------------------------- /a1/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 | -------------------------------------------------------------------------------- /a1/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 | -------------------------------------------------------------------------------- /a1/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 | -------------------------------------------------------------------------------- /a1/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 | -------------------------------------------------------------------------------- /a1/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 | -------------------------------------------------------------------------------- /a1/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 | -------------------------------------------------------------------------------- /a1/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 | -------------------------------------------------------------------------------- /a1/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 | -------------------------------------------------------------------------------- /a1/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 | -------------------------------------------------------------------------------- /a1/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 | } -------------------------------------------------------------------------------- /a1/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 | } -------------------------------------------------------------------------------- /a1/assets/webstorm-angular-live-templates/angular.app.webstorm-live-template.xml: -------------------------------------------------------------------------------- 1 | 2 | 33 | -------------------------------------------------------------------------------- /a1/assets/webstorm-angular-live-templates/angular.component.webstorm-live-template.xml: -------------------------------------------------------------------------------- 1 | 2 | 36 | 37 | -------------------------------------------------------------------------------- /a1/assets/webstorm-angular-live-templates/angular.config.webstorm-live-template.xml: -------------------------------------------------------------------------------- 1 | 2 | 34 | -------------------------------------------------------------------------------- /a1/assets/webstorm-angular-live-templates/angular.controller.webstorm-live-template.xml: -------------------------------------------------------------------------------- 1 | 2 | 35 | -------------------------------------------------------------------------------- /a1/assets/webstorm-angular-live-templates/angular.directive.webstorm-live-template.xml: -------------------------------------------------------------------------------- 1 | 2 | 37 | 38 | -------------------------------------------------------------------------------- /a1/assets/webstorm-angular-live-templates/angular.factory.webstorm-live-template.xml: -------------------------------------------------------------------------------- 1 | 2 | 36 | -------------------------------------------------------------------------------- /a1/assets/webstorm-angular-live-templates/angular.filter.webstorm-live-template.xml: -------------------------------------------------------------------------------- 1 | 2 | 34 | -------------------------------------------------------------------------------- /a1/assets/webstorm-angular-live-templates/angular.module.webstorm-live-template.xml: -------------------------------------------------------------------------------- 1 | 2 | 31 | -------------------------------------------------------------------------------- /a1/assets/webstorm-angular-live-templates/angular.route.webstorm-live-template.xml: -------------------------------------------------------------------------------- 1 | 2 | 35 | -------------------------------------------------------------------------------- /a1/assets/webstorm-angular-live-templates/angular.run.webstorm-live-template.xml: -------------------------------------------------------------------------------- 1 | 2 | 34 | -------------------------------------------------------------------------------- /a1/assets/webstorm-angular-live-templates/angular.service.webstorm-live-template.xml: -------------------------------------------------------------------------------- 1 | 2 | 36 | -------------------------------------------------------------------------------- /a1/assets/webstorm-angular-live-templates/angular.state.webstorm-live-template.xml: -------------------------------------------------------------------------------- 1 | 2 | 36 | -------------------------------------------------------------------------------- /a1/assets/webstorm-angular-live-templates/webstorm-angular-live-templates.xml: -------------------------------------------------------------------------------- 1 | 2 | 36 | 69 | 104 | 138 | 170 | 201 | 235 | 267 | 301 | 333 | 362 | 395 | 396 | -------------------------------------------------------------------------------- /a1/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 | 1. [Korean](ko-KR.md) by [Youngjae Ji](https://github.com/zirho) 17 | 1. [Turkish](tr-TR.md) by [Uğur Korfalı](https://github.com/kel-sakal-biyik) 18 | 19 | ## Contributing 20 | 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. 21 | 22 | All translations must preserve the intention of the original document. 23 | 24 | > 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. 25 | 26 | ### New Translations 27 | 1. Fork the repository 28 | 2. Create a translation file and name it using the 118n standard format. 29 | 3. Put this file in the i18n folder 30 | 4. Translate the original English version to be current with the latest changes 31 | 3. Make a Pull Request 32 | 33 | Once you do these I will merge, point the translation links to it, and enter the translation credit to you. 34 | 35 | ### Updated Translations 36 | 1. Fork the repository 37 | 2. Make the translation changes 38 | 3. Make a Pull Request 39 | 40 | Once you do these I will merge, point the translation links to it, and enter the translation credit to you. 41 | 42 | -------------------------------------------------------------------------------- /a1/i18n/zh-CN.md: -------------------------------------------------------------------------------- 1 | # Angular规范 2 | 3 | ## Angular Team Endorsed 4 | 非常感谢领导Angular团队的Igor Minar对本指南做出的审查和贡献,并且委托我继续打理本指南。 5 | 6 | ## 目的 7 | *Angular规范[@john_papa](//twitter.com/john_papa)* 8 | 9 | 如果你正在寻找一些关于语法、约定和结构化的Angular应用的一个有建设性的规范,那么你来对地方了。这里所包含的内容是基于我在团队中使用[Angular](//angularjs.org)的一些经验、一些演讲和[Pluralsight培训课程](http://pluralsight.com/training/Authors/Details/john-papa)。 10 | 11 | 这个规范的目的是为构建Angular应用提供指导,当然更加重要的是让大家知道我为什么要选择它们。 12 | 13 | >如果你喜欢这个规范,请在Pluralsight看看[Angular Patterns: Clean Code](http://jpapa.me/ngclean)。 14 | 15 | [![Angular Patterns: Clean Code](https://raw.githubusercontent.com/johnpapa/angular-styleguide/master/a1/assets/ng-clean-code-banner.png)](http://jpapa.me/ngclean) 16 | 17 | ## Community Awesomeness and Credit 18 | Angular社区是一个热衷于分享经验的令人难以置信的社区,尽管Todd Motto(他是我的一个朋友,也是Angular专家)和我合作了多种规范和惯例,但是我们也存在着一些分歧。我鼓励你去看看[Todd的指南](https://github.com/toddmotto/angularjs-styleguide),在那里你能看到我们之间的区别。 19 | 20 | 我的许多规范都是从大量的程序会话[Ward Bell](http://twitter.com/wardbell)和我所拥有的而来的,我的好友Ward也影响了本规范的最终演变。 21 | 22 | ## 在示例App中了解这些规范 23 | 看示例代码有助于你更好地理解,你可以在`modular`文件夹下找到[命名为modular的示例应用程序](https://github.com/johnpapa/ng-demos),随便克隆。 24 | 25 | ## 翻译 26 | [Angular规范翻译版本](https://github.com/johnpapa/angular-styleguide/tree/master/a1/i18n)。 27 | 28 | ## 目录 29 | 1. [单一职责](#单一职责) 30 | 1. [IIFE](#iife) 31 | 1. [Modules](#modules) 32 | 1. [Controllers](#controllers) 33 | 1. [Services](#services) 34 | 1. [Factories](#factories) 35 | 1. [Data Services](#data-services) 36 | 1. [Directives](#directives) 37 | 1. [解决Controller的Promises](#解决controller的promises) 38 | 1. [手动依赖注入](#手动依赖注入) 39 | 1. [压缩和注释](#压缩和注释) 40 | 1. [异常处理](#异常处理) 41 | 1. [命名](#命名) 42 | 1. [应用程序结构的LIFT准则](#应用程序结构的lift准则) 43 | 1. [应用程序结构](#应用程序结构) 44 | 1. [模块化](#模块化) 45 | 1. [启动逻辑](#启动逻辑) 46 | 1. [Angular $包装服务](#angular-包装服务) 47 | 1. [测试](#测试) 48 | 1. [动画](#动画) 49 | 1. [注释](#注释) 50 | 1. [JSHint](#js-hint) 51 | 1. [JSCS](#jscs) 52 | 1. [常量](#常量) 53 | 1. [文件模板和片段](#文件模板和片段) 54 | 1. [Yeoman Generator](#yeoman-generator) 55 | 1. [路由](#路由) 56 | 1. [任务自动化](#任务自动化) 57 | 1. [Filters](#filters) 58 | 1. [Angular文档](#angularjs文档) 59 | 1. [贡献](#贡献) 60 | 1. [许可](#许可) 61 | 62 | ## 单一职责 63 | ### 规则一 64 | ###### [Style [Y001](#style-y001)] 65 | 66 | - 一个文件只定义一个组件。 67 | 68 | 下面的例子在同一个文件中定义了一个`app`的module和它的一些依赖、一个controller和一个factory。 69 | 70 | ```javascript 71 | /* avoid */ 72 | angular 73 | .module('app', ['ngRoute']) 74 | .controller('SomeController', SomeController) 75 | .factory('someFactory', someFactory); 76 | 77 | function SomeController() { } 78 | 79 | function someFactory() { } 80 | ``` 81 | 82 | 推荐以下面的方式来做,把上面相同的组件分割成单独的文件。 83 | ```javascript 84 | /* recommended */ 85 | 86 | // app.module.js 87 | angular 88 | .module('app', ['ngRoute']); 89 | ``` 90 | 91 | ```javascript 92 | /* recommended */ 93 | 94 | // someController.js 95 | angular 96 | .module('app') 97 | .controller('SomeController', SomeController); 98 | 99 | function SomeController() { } 100 | ``` 101 | 102 | ```javascript 103 | /* recommended */ 104 | // someFactory.js 105 | angular 106 | .module('app') 107 | .factory('someFactory', someFactory); 108 | 109 | function someFactory() { } 110 | ``` 111 | 112 | **[返回顶部](#目录)** 113 | 114 | ## IIFE 115 | ### JavaScript闭包 116 | ###### [Style [Y010](#style-y010)] 117 | 118 | - 把Angular组件包装到一个立即调用函数表达式中(IIFE)。 119 | 120 | *为什么?*:把变量从全局作用域中删除了,这有助于防止变量和函数声明比预期在全局作用域中有更长的生命周期,也有助于避免变量冲突。 121 | 122 | *为什么?*:当你的代码为了发布而压缩了并且被合并到同一个文件中时,可能会有很多变量发生冲突,使用了IIFE(给每个文件提供了一个独立的作用域),你就不用担心这个了。 123 | 124 | ```javascript 125 | /* avoid */ 126 | // logger.js 127 | angular 128 | .module('app') 129 | .factory('logger', logger); 130 | 131 | // logger function会被当作一个全局变量 132 | function logger() { } 133 | 134 | // storage.js 135 | angular 136 | .module('app') 137 | .factory('storage', storage); 138 | 139 | // storage function会被当作一个全局变量 140 | function storage() { } 141 | ``` 142 | 143 | ```javascript 144 | /** 145 | * recommended 146 | * 147 | * 再也不存在全局变量了 148 | */ 149 | 150 | // logger.js 151 | (function() { 152 | 'use strict'; 153 | 154 | angular 155 | .module('app') 156 | .factory('logger', logger); 157 | 158 | function logger() { } 159 | })(); 160 | 161 | // storage.js 162 | (function() { 163 | 'use strict'; 164 | 165 | angular 166 | .module('app') 167 | .factory('storage', storage); 168 | 169 | function storage() { } 170 | })(); 171 | ``` 172 | 173 | - 注:为了简洁起见,本规范余下的示例中将会省略IIFE语法。 174 | 175 | - 注:IIFE阻止了测试代码访问私有成员(正则表达式、helper函数等),这对于自身测试是非常友好的。然而你可以把这些私有成员暴露到可访问成员中进行测试,例如把私有成员(正则表达式、helper函数等)放到factory或是constant中。 176 | 177 | **[返回顶部](#目录)** 178 | 179 | ## Modules 180 | 181 | ### 避免命名冲突 182 | ###### [Style [Y020](#style-y020)] 183 | 184 | - 每一个独立子模块使用唯一的命名约定。 185 | 186 | *为什么*:避免冲突,每个模块也可以方便定义子模块。 187 | 188 | ### 定义(aka Setters) 189 | ###### [Style [Y021](#style-y021)] 190 | 191 | - 不使用任何一个使用了setter语法的变量来定义modules。 192 | 193 | *为什么?*:在一个文件只有一个组件的条件下,完全不需要为一个模块引入一个变量。 194 | 195 | ```javascript 196 | /* avoid */ 197 | var app = angular.module('app', [ 198 | 'ngAnimate', 199 | 'ngRoute', 200 | 'app.shared', 201 | 'app.dashboard' 202 | ]); 203 | ``` 204 | 205 | 你只需要用简单的setter语法来代替。 206 | 207 | ```javascript 208 | /* recommended */ 209 | angular 210 | .module('app', [ 211 | 'ngAnimate', 212 | 'ngRoute', 213 | 'app.shared', 214 | 'app.dashboard' 215 | ]); 216 | ``` 217 | 218 | ### Getters 219 | ###### [Style [Y022](#style-y022)] 220 | 221 | - 使用module的时候,避免直接用一个变量,而是使用getter的链式语法。 222 | 223 | *为什么?*:这将产生更加易读的代码,并且可以避免变量冲突和泄漏。 224 | 225 | ```javascript 226 | /* avoid */ 227 | var app = angular.module('app'); 228 | app.controller('SomeController', SomeController); 229 | 230 | function SomeController() { } 231 | ``` 232 | 233 | ```javascript 234 | /* recommended */ 235 | angular 236 | .module('app') 237 | .controller('SomeController', SomeController); 238 | 239 | function SomeController() { } 240 | ``` 241 | 242 | ### Setting vs Getting 243 | ###### [Style [Y023](#style-y023)] 244 | 245 | - 只能设置一次。 246 | 247 | *为什么?*:一个module只能被创建一次,创建之后才能被检索到。 248 | 249 | - 设置module,`angular.module('app', []);`。 250 | - 获取module,`angular.module('app');`。 251 | 252 | ### 命名函数 vs 匿名函数 253 | ###### [Style [Y024](#style-y024)] 254 | 255 | - 回调函数使用命名函数,不要用匿名函数。 256 | 257 | *为什么?*:易读,方便调试,减少嵌套回调函数的数量。 258 | 259 | ```javascript 260 | /* avoid */ 261 | angular 262 | .module('app') 263 | .controller('Dashboard', function() { }) 264 | .factory('logger', function() { }); 265 | ``` 266 | 267 | ```javascript 268 | /* recommended */ 269 | 270 | // dashboard.js 271 | angular 272 | .module('app') 273 | .controller('Dashboard', Dashboard); 274 | 275 | function Dashboard () { } 276 | ``` 277 | 278 | ```javascript 279 | // logger.js 280 | angular 281 | .module('app') 282 | .factory('logger', logger); 283 | 284 | function logger () { } 285 | ``` 286 | 287 | **[回到顶部](#目录)** 288 | 289 | ## Controllers 290 | 291 | ### controllerAs在View中的语法 292 | ###### [Style [Y030](#style-y030)] 293 | 294 | - 使用[`controllerAs`](http://www.johnpapa.net/do-you-like-your-angular-controllers-with-or-without-sugar/) 语法代替直接用经典的$scope定义的controller的方式。 295 | 296 | *为什么?*:controller被构建的时候,就会有一个新的实例,`controllerAs` 的语法比`经典的$scope语法`更接近JavaScript构造函数。 297 | 298 | *为什么?*:这促进在View中对绑定到“有修饰”的对象的使用(例如用`customer.name` 代替`name`),这将更有语境、更容易阅读,也避免了任何没有“修饰”而产生的引用问题。 299 | 300 | *为什么?*:有助于避免在有嵌套的controllers的Views中调用 `$parent`。 301 | 302 | ```html 303 | 304 |
305 | {{ name }} 306 |
307 | ``` 308 | 309 | ```html 310 | 311 |
312 | {{ customer.name }} 313 |
314 | ``` 315 | 316 | ### controllerAs在controller中的语法 317 | ###### [Style [Y031](#style-y031)] 318 | 319 | - 使用 `controllerAs` 语法代替 `经典的$scope语法` 语法。 320 | 321 | - 使用`controllerAs` 时,controller中的`$scope`被绑定到了`this`上。 322 | 323 | *为什么?*:`controllerAs` 是`$scope`的语法修饰,你仍然可以绑定到View上并且访问 `$scope`的方法。 324 | 325 | *为什么?*:避免在controller中使用 `$scope`,最好不用它们或是把它们移到一个factory中。factory中可以考虑使用`$scope`,controller中只在需要时候才使用`$scope`,例如当使用[`$emit`](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$emit), [`$broadcast`](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$broadcast),或者 [`$on`](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$on)。 326 | 327 | ```javascript 328 | /* avoid */ 329 | function Customer ($scope) { 330 | $scope.name = {}; 331 | $scope.sendMessage = function() { }; 332 | } 333 | ``` 334 | 335 | ```javascript 336 | /* recommended - but see next section */ 337 | function Customer () { 338 | this.name = {}; 339 | this.sendMessage = function() { }; 340 | } 341 | ``` 342 | 343 | ### controllerAs with vm 344 | 345 | ###### [Style [Y032](#style-y032)] 346 | 347 | - 使用`controllerAs`语法时把`this` 赋值给一个可捕获的变量,选择一个有代表性的名称,例如`vm`代表ViewModel。 348 | 349 | *为什么?*:`this`在不同的地方有不同的语义(就是作用域不同),在controller中的一个函数内部使用`this`时可能会改变它的上下文。用一个变量来捕获`this`的上下文从而可以避免遇到这样的坑。 350 | 351 | ```javascript 352 | /* avoid */ 353 | function Customer() { 354 | this.name = {}; 355 | this.sendMessage = function() { }; 356 | } 357 | ``` 358 | 359 | ```javascript 360 | /* recommended */ 361 | function Customer () { 362 | var vm = this; 363 | vm.name = {}; 364 | vm.sendMessage = function() { }; 365 | } 366 | ``` 367 | 368 | - 注:你可以参照下面的做法来避免 [jshint](http://www.jshint.com/)的警告。但是构造函数(函数名首字母大写)是不需要这个的. 369 | 370 | ```javascript 371 | /* jshint validthis: true */ 372 | var vm = this; 373 | ``` 374 | - 注:在controller中用`controller as`创建了一个watch时,可以用下面的语法监测`vm.*`的成员。(创建watch时要谨慎,因为它会增加更多的负载) 375 | 376 | ```html 377 | 378 | ``` 379 | 380 | ```javascript 381 | function SomeController($scope, $log) { 382 | var vm = this; 383 | vm.title = 'Some Title'; 384 | 385 | $scope.$watch('vm.title', function(current, original) { 386 | $log.info('vm.title was %s', original); 387 | $log.info('vm.title is now %s', current); 388 | }); 389 | } 390 | ``` 391 | 392 | ### 置顶绑定成员 393 | 394 | ###### [Style [Y033](#style-y033)] 395 | 396 | - 把可绑定的成员放到controller的顶部,按字母排序,并且不要通过controller的代码传播。 397 | 398 | *为什么?*:虽然设置单行匿名函数很容易,但是当这些函数的代码超过一行时,这将极大降低代码的可读性。在可绑定成员下面定义函数(这些函数被提出来),把具体的实现细节放到下面,可绑定成员置顶,这会提高代码的可读性。 399 | 400 | ```javascript 401 | /* avoid */ 402 | function Sessions() { 403 | var vm = this; 404 | 405 | vm.gotoSession = function() { 406 | /* ... */ 407 | }; 408 | vm.refresh = function() { 409 | /* ... */ 410 | }; 411 | vm.search = function() { 412 | /* ... */ 413 | }; 414 | vm.sessions = []; 415 | vm.title = 'Sessions'; 416 | ``` 417 | 418 | ```javascript 419 | /* recommended */ 420 | function Sessions() { 421 | var vm = this; 422 | 423 | vm.gotoSession = gotoSession; 424 | vm.refresh = refresh; 425 | vm.search = search; 426 | vm.sessions = []; 427 | vm.title = 'Sessions'; 428 | 429 | //////////// 430 | 431 | function gotoSession() { 432 | /* */ 433 | } 434 | 435 | function refresh() { 436 | /* */ 437 | } 438 | 439 | function search() { 440 | /* */ 441 | } 442 | ``` 443 | 444 | ![Controller Using "Above the Fold"](https://raw.githubusercontent.com/johnpapa/angular-styleguide/master/a1/assets/above-the-fold-1.png) 445 | 446 | 注:如果一个函数就是一行,那么只要不影响可读性就把它放到顶部。 447 | 448 | ```javascript 449 | /* avoid */ 450 | function Sessions(data) { 451 | var vm = this; 452 | 453 | vm.gotoSession = gotoSession; 454 | vm.refresh = function() { 455 | /** 456 | * lines 457 | * of 458 | * code 459 | * affects 460 | * readability 461 | */ 462 | }; 463 | vm.search = search; 464 | vm.sessions = []; 465 | vm.title = 'Sessions'; 466 | ``` 467 | 468 | ```javascript 469 | /* recommended */ 470 | function Sessions(dataservice) { 471 | var vm = this; 472 | 473 | vm.gotoSession = gotoSession; 474 | vm.refresh = dataservice.refresh; // 1 liner is OK 475 | vm.search = search; 476 | vm.sessions = []; 477 | vm.title = 'Sessions'; 478 | ``` 479 | 480 | ### 函数声明隐藏实现细节 481 | ###### [Style [Y034](#style-y034)] 482 | 483 | - 使用函数声明来隐藏实现细节,置顶绑定成员,当你需要在controller中绑定一个函数时,把它指向一个在文件的后面会出现函数声明。更多详情请看[这里](http://www.johnpapa.net/angular-function-declarations-function-expressions-and-readable-code)。 484 | 485 | *为什么?*:易读,易识别哪些成员可以在View中绑定和使用。 486 | 487 | *为什么?*:把函数的实现细节放到后面,你可以更清楚地看到重要的东西。 488 | 489 | *为什么?*:由于函数声明会被置顶,所以没有必要担心在声明它之前就使用函数的问题。 490 | 491 | *为什么?*:你再也不用担心当 `a`依赖于 `b`时,把`var a`放到`var b`之前会中断你的代码的函数声明问题。 492 | 493 | *为什么?*:函数表达式中顺序是至关重要的。 494 | 495 | ```javascript 496 | /** 497 | * avoid 498 | * Using function expressions. 499 | */ 500 | function Avengers(dataservice, logger) { 501 | var vm = this; 502 | vm.avengers = []; 503 | vm.title = 'Avengers'; 504 | 505 | var activate = function() { 506 | return getAvengers().then(function() { 507 | logger.info('Activated Avengers View'); 508 | }); 509 | } 510 | 511 | var getAvengers = function() { 512 | return dataservice.getAvengers().then(function(data) { 513 | vm.avengers = data; 514 | return vm.avengers; 515 | }); 516 | } 517 | 518 | vm.getAvengers = getAvengers; 519 | 520 | activate(); 521 | } 522 | ``` 523 | 524 | 注意这里重要的代码分散在前面的例子中。 525 | 下面的示例中,可以看到重要的代码都放到了顶部。实现的详细细节都在下方,显然这样的代码更易读。 526 | 527 | ```javascript 528 | /* 529 | * recommend 530 | * Using function declarations 531 | * and bindable members up top. 532 | */ 533 | function Avengers(dataservice, logger) { 534 | var vm = this; 535 | vm.avengers = []; 536 | vm.getAvengers = getAvengers; 537 | vm.title = 'Avengers'; 538 | 539 | activate(); 540 | 541 | function activate() { 542 | return getAvengers().then(function() { 543 | logger.info('Activated Avengers View'); 544 | }); 545 | } 546 | 547 | function getAvengers() { 548 | return dataservice.getAvengers().then(function(data) { 549 | vm.avengers = data; 550 | return vm.avengers; 551 | }); 552 | } 553 | } 554 | ``` 555 | 556 | ### 把Controller中的逻辑延迟到Service中 557 | ###### [Style [Y035](#style-y035)] 558 | 559 | - 通过委派到service和factory中来延迟controller中的逻辑。 560 | 561 | *为什么?*:把逻辑放到service中,并通过一个function暴露,就可以被多个controller重用。 562 | 563 | *为什么?*:把逻辑放到service中将会使单元测试的时候更加容易地把它们分离,相反,如果在controller中调用逻辑就显得很二了。 564 | 565 | *为什么?*:保持controller的简洁。 566 | 567 | *为什么?*:从controller中删除依赖关系并且隐藏实现细节。 568 | 569 | ```javascript 570 | /* avoid */ 571 | function Order($http, $q, config, userInfo) { 572 | var vm = this; 573 | vm.checkCredit = checkCredit; 574 | vm.isCreditOk; 575 | vm.total = 0; 576 | 577 | function checkCredit () { 578 | var settings = {}; 579 | // Get the credit service base URL from config 580 | // Set credit service required headers 581 | // Prepare URL query string or data object with request data 582 | // Add user-identifying info so service gets the right credit limit for this user. 583 | // Use JSONP for this browser if it doesn't support CORS 584 | return $http.get(settings) 585 | .then(function(data) { 586 | // Unpack JSON data in the response object 587 | // to find maxRemainingAmount 588 | vm.isCreditOk = vm.total <= maxRemainingAmount 589 | }) 590 | .catch(function(error) { 591 | // Interpret error 592 | // Cope w/ timeout? retry? try alternate service? 593 | // Re-reject with appropriate error for a user to see 594 | }); 595 | }; 596 | } 597 | ``` 598 | 599 | ```javascript 600 | /* recommended */ 601 | function Order (creditService) { 602 | var vm = this; 603 | vm.checkCredit = checkCredit; 604 | vm.isCreditOk; 605 | vm.total = 0; 606 | 607 | function checkCredit () { 608 | return creditService.isOrderTotalOk(vm.total) 609 | .then(function(isOk) { vm.isCreditOk = isOk; }) 610 | .catch(showServiceError); 611 | }; 612 | } 613 | ``` 614 | 615 | ### 保持Controller的专一性 616 | ###### [Style [Y037](#style-y037)] 617 | 618 | - 一个view定义一个controller,尽量不要在其它view中使用这个controller。把可重用的逻辑放到factory中,保证controller只服务于当前视图。 619 | 620 | *为什么?*:不同的view用同一个controller是非常不科学的,良好的端对端测试覆盖率对于保证大型应用稳定性是必需的。 621 | 622 | ### 分配Controller 623 | ###### [Style [Y038](#style-y038)] 624 | 625 | - 当一个controller必须匹配一个view时或者任何一个组件可能被其它controller或是view重用时,连同controller的route一起定义。 626 | 627 | 注:如果一个view是通过route外的其它形式加载的,那么就用`ng-controller="Avengers as vm"`语法。 628 | 629 | *为什么?*:在route中匹配controller允许不同的路由调用不同的相匹配的controller和view,当在view中通过[`ng-controller`](https://docs.angularjs.org/api/ng/directive/ngController)分配controller时,这个view总是和相同的controller相关联。 630 | 631 | ```javascript 632 | /* avoid - when using with a route and dynamic pairing is desired */ 633 | 634 | // route-config.js 635 | angular 636 | .module('app') 637 | .config(config); 638 | 639 | function config ($routeProvider) { 640 | $routeProvider 641 | .when('/avengers', { 642 | templateUrl: 'avengers.html' 643 | }); 644 | } 645 | ``` 646 | 647 | ```html 648 | 649 |
650 |
651 | ``` 652 | 653 | ```javascript 654 | /* recommended */ 655 | 656 | // route-config.js 657 | angular 658 | .module('app') 659 | .config(config); 660 | 661 | function config ($routeProvider) { 662 | $routeProvider 663 | .when('/avengers', { 664 | templateUrl: 'avengers.html', 665 | controller: 'Avengers', 666 | controllerAs: 'vm' 667 | }); 668 | } 669 | ``` 670 | 671 | ```html 672 | 673 |
674 |
675 | ``` 676 | 677 | **[返回顶部](#目录)** 678 | 679 | ## Services 680 | 681 | ### 单例 682 | ###### [Style [Y040](#style-y040)] 683 | 684 | - 用`new`实例化service,用`this`实例化公共方法和变量,由于这和factory是类似的,所以为了保持统一,推荐用facotry来代替。 685 | 686 | 注意:[所有的Angular services都是单例](https://docs.angularjs.org/guide/services),这意味着每个injector都只有一个实例化的service。 687 | 688 | ```javascript 689 | // service 690 | angular 691 | .module('app') 692 | .service('logger', logger); 693 | 694 | function logger () { 695 | this.logError = function(msg) { 696 | /* */ 697 | }; 698 | } 699 | ``` 700 | 701 | ```javascript 702 | // factory 703 | angular 704 | .module('app') 705 | .factory('logger', logger); 706 | 707 | function logger () { 708 | return { 709 | logError: function(msg) { 710 | /* */ 711 | } 712 | }; 713 | } 714 | ``` 715 | 716 | **[返回顶部](#目录)** 717 | 718 | ## Factories 719 | 720 | ### 单一职责 721 | ###### [Style [Y051](#style-y051)] 722 | 723 | - factory应该是[单一职责](http://en.wikipedia.org/wiki/Single_responsibility_principle),这是由其上下文进行封装的。一旦一个factory将要处理超过单一的目的时,就应该创建一个新的factory。 724 | 725 | ### 单例 726 | ###### [Style [Y051](#style-y051)] 727 | 728 | - facotry是一个单例,它返回一个包含service成员的对象。 729 | 730 | 注:[所有的Angular services都是单例](https://docs.angularjs.org/guide/services),这意味着每个injector都只有一个实例化的service。 731 | 732 | ### 可访问的成员置顶 733 | ###### [Style [Y052](#style-y052)] 734 | 735 | - 使用从[显露模块模式](http://addyosmani.com/resources/essentialjsdesignpatterns/book/#revealingmodulepatternjavascript)派生出来的技术把service(它的接口)中可调用的成员暴露到顶部, 736 | 737 | *为什么?*:易读,并且让你可以立即识别service中的哪些成员可以被调用,哪些成员必须进行单元测试(或者被别人嘲笑)。 738 | 739 | *为什么?*:当文件内容很长时,这可以避免需要滚动才能看到暴露了哪些东西。 740 | 741 | *为什么?*:虽然你可以随意写一个函数,但当函数代码超过一行时就会降低可读性并造成滚动。通过把实现细节放下面、把可调用接口置顶的形式返回service的方式来定义可调用的接口,从而使代码更加易读。 742 | 743 | ```javascript 744 | /* avoid */ 745 | function dataService () { 746 | var someValue = ''; 747 | function save () { 748 | /* */ 749 | }; 750 | function validate () { 751 | /* */ 752 | }; 753 | 754 | return { 755 | save: save, 756 | someValue: someValue, 757 | validate: validate 758 | }; 759 | } 760 | ``` 761 | 762 | ```javascript 763 | /* recommended */ 764 | function dataService () { 765 | var someValue = ''; 766 | var service = { 767 | save: save, 768 | someValue: someValue, 769 | validate: validate 770 | }; 771 | return service; 772 | 773 | //////////// 774 | 775 | function save () { 776 | /* */ 777 | }; 778 | 779 | function validate () { 780 | /* */ 781 | }; 782 | } 783 | ``` 784 | 785 | 这种绑定方式复制了宿主对象,原始值不会随着暴露模块模式的使用而更新。 786 | 787 | ![Factories Using "Above the Fold"](https://raw.githubusercontent.com/johnpapa/angular-styleguide/master/a1/assets/above-the-fold-2.png) 788 | 789 | ### 函数声明隐藏实现细节 790 | ###### [Style [Y053](#style-y053)] 791 | 792 | - 函数声明隐藏实现细节,置顶绑定成员,当你需要在controller中绑定一个函数时,把它指向一个函数声明,这个函数声明在文件的后面会出现。 793 | 794 | *为什么?*:易读,易识别哪些成员可以在View中绑定和使用。 795 | 796 | *为什么?*:把函数的实现细节放到后面,你可以更清楚地看到重要的东西。 797 | 798 | *为什么?*:由于函数声明会被置顶,所以没有必要担心在声明它之前就使用函数的问题。 799 | 800 | *为什么?*:你再也不用担心当 `a`依赖于 `b`时,把`var a`放到`var b`之前会中断你的代码的函数声明问题。 801 | 802 | *为什么?*:函数表达式中顺序是至关重要的。 803 | 804 | ```javascript 805 | /** 806 | * avoid 807 | * Using function expressions 808 | */ 809 | function dataservice($http, $location, $q, exception, logger) { 810 | var isPrimed = false; 811 | var primePromise; 812 | 813 | var getAvengers = function() { 814 | // implementation details go here 815 | }; 816 | 817 | var getAvengerCount = function() { 818 | // implementation details go here 819 | }; 820 | 821 | var getAvengersCast = function() { 822 | // implementation details go here 823 | }; 824 | 825 | var prime = function() { 826 | // implementation details go here 827 | }; 828 | 829 | var ready = function(nextPromises) { 830 | // implementation details go here 831 | }; 832 | 833 | var service = { 834 | getAvengersCast: getAvengersCast, 835 | getAvengerCount: getAvengerCount, 836 | getAvengers: getAvengers, 837 | ready: ready 838 | }; 839 | 840 | return service; 841 | } 842 | ``` 843 | 844 | ```javascript 845 | /** 846 | * recommended 847 | * Using function declarations 848 | * and accessible members up top. 849 | */ 850 | function dataservice($http, $location, $q, exception, logger) { 851 | var isPrimed = false; 852 | var primePromise; 853 | 854 | var service = { 855 | getAvengersCast: getAvengersCast, 856 | getAvengerCount: getAvengerCount, 857 | getAvengers: getAvengers, 858 | ready: ready 859 | }; 860 | 861 | return service; 862 | 863 | //////////// 864 | 865 | function getAvengers() { 866 | // implementation details go here 867 | } 868 | 869 | function getAvengerCount() { 870 | // implementation details go here 871 | } 872 | 873 | function getAvengersCast() { 874 | // implementation details go here 875 | } 876 | 877 | function prime() { 878 | // implementation details go here 879 | } 880 | 881 | function ready(nextPromises) { 882 | // implementation details go here 883 | } 884 | } 885 | ``` 886 | 887 | **[返回顶部](#目录)** 888 | 889 | ## Data Services 890 | 891 | ### 独立的数据调用 892 | ###### [Style [Y060](#style-y060)] 893 | 894 | - 把进行数据操作和数据交互的逻辑放到factory中,数据服务负责XHR请求、本地存储、内存存储和其它任何数据操作。 895 | 896 | *为什么?*:controller的作用是查看视图和收集视图的信息,它不应该关心如何取得数据,只需要知道哪里需要用到数据。把取数据的逻辑放到数据服务中能够让controller更简单、更专注于对view的控制。 897 | 898 | *为什么?*:方便测试。 899 | 900 | *为什么?*:数据服务的实现可能有非常明确的代码来处理数据仓库,这可能包含headers、如何与数据交互或是其它service,例如`$http`。把逻辑封装到单独的数据服务中,这隐藏了外部调用者(例如controller)对数据的直接操作,这样更加容易执行变更。 901 | 902 | ```javascript 903 | /* recommended */ 904 | 905 | // dataservice factory 906 | angular 907 | .module('app.core') 908 | .factory('dataservice', dataservice); 909 | 910 | dataservice.$inject = ['$http', 'logger']; 911 | 912 | function dataservice($http, logger) { 913 | return { 914 | getAvengers: getAvengers 915 | }; 916 | 917 | function getAvengers() { 918 | return $http.get('/api/maa') 919 | .then(getAvengersComplete) 920 | .catch(getAvengersFailed); 921 | 922 | function getAvengersComplete(response) { 923 | return response.data.results; 924 | } 925 | 926 | function getAvengersFailed(error) { 927 | logger.error('XHR Failed for getAvengers.' + error.data); 928 | } 929 | } 930 | } 931 | ``` 932 | 933 | 注意:数据服务被调用时(例如controller),隐藏调用的直接行为,如下所示。 934 | 935 | ```javascript 936 | /* recommended */ 937 | 938 | // controller calling the dataservice factory 939 | angular 940 | .module('app.avengers') 941 | .controller('Avengers', Avengers); 942 | 943 | Avengers.$inject = ['dataservice', 'logger']; 944 | 945 | function Avengers(dataservice, logger) { 946 | var vm = this; 947 | vm.avengers = []; 948 | 949 | activate(); 950 | 951 | function activate() { 952 | return getAvengers().then(function() { 953 | logger.info('Activated Avengers View'); 954 | }); 955 | } 956 | 957 | function getAvengers() { 958 | return dataservice.getAvengers() 959 | .then(function(data) { 960 | vm.avengers = data; 961 | return vm.avengers; 962 | }); 963 | } 964 | } 965 | ``` 966 | 967 | ### 数据调用返回一个Promise 968 | ###### [Style [Y061](#style-y061)] 969 | 970 | - 就像`$http`一样,调用数据时返回一个promise,在你的调用函数中也返回一个promise。 971 | 972 | *为什么?*:你可以把promise链接到一起,在数据调用完成并且resolve或是reject这个promise后采取进一步的行为。 973 | 974 | ```javascript 975 | /* recommended */ 976 | 977 | activate(); 978 | 979 | function activate() { 980 | /** 981 | * Step 1 982 | * Ask the getAvengers function for the 983 | * avenger data and wait for the promise 984 | */ 985 | return getAvengers().then(function() { 986 | /** 987 | * Step 4 988 | * Perform an action on resolve of final promise 989 | */ 990 | logger.info('Activated Avengers View'); 991 | }); 992 | } 993 | 994 | function getAvengers() { 995 | /** 996 | * Step 2 997 | * Ask the data service for the data and wait 998 | * for the promise 999 | */ 1000 | return dataservice.getAvengers() 1001 | .then(function(data) { 1002 | /** 1003 | * Step 3 1004 | * set the data and resolve the promise 1005 | */ 1006 | vm.avengers = data; 1007 | return vm.avengers; 1008 | }); 1009 | } 1010 | ``` 1011 | 1012 | **[返回顶部](#目录)** 1013 | 1014 | ## Directives 1015 | ### 一个directive一个文件 1016 | ###### [Style [Y070](#style-y070)] 1017 | 1018 | - 一个文件中只创建一个directive,并依照directive来命名文件。 1019 | 1020 | *为什么?*:虽然把所有directive放到一个文件中很简单,但是当一些directive是跨应用的,一些是跨模块的,一些仅仅在一个模块中使用时,想把它们独立出来就非常困难了。 1021 | 1022 | *为什么?*:一个文件一个directive也更加容易维护。 1023 | 1024 | > 注: "**最佳实践**:Angular文档中有提过,directive应该自动回收,当directive被移除后,你可以使用`element.on('$destroy', ...)`或者`scope.$on('$destroy', ...)`来执行一个clean-up函数。" 1025 | 1026 | ```javascript 1027 | /* avoid */ 1028 | /* directives.js */ 1029 | 1030 | angular 1031 | .module('app.widgets') 1032 | 1033 | /* order directive仅仅会被order module用到 */ 1034 | .directive('orderCalendarRange', orderCalendarRange) 1035 | 1036 | /* sales directive可以在sales app的任意地方使用 */ 1037 | .directive('salesCustomerInfo', salesCustomerInfo) 1038 | 1039 | /* spinner directive可以在任意apps中使用 */ 1040 | .directive('sharedSpinner', sharedSpinner); 1041 | 1042 | function orderCalendarRange() { 1043 | /* implementation details */ 1044 | } 1045 | 1046 | function salesCustomerInfo() { 1047 | /* implementation details */ 1048 | } 1049 | 1050 | function sharedSpinner() { 1051 | /* implementation details */ 1052 | } 1053 | ``` 1054 | 1055 | ```javascript 1056 | /* recommended */ 1057 | /* calendarRange.directive.js */ 1058 | 1059 | /** 1060 | * @desc order directive that is specific to the order module at a company named Acme 1061 | * @example
1062 | */ 1063 | angular 1064 | .module('sales.order') 1065 | .directive('acmeOrderCalendarRange', orderCalendarRange); 1066 | 1067 | function orderCalendarRange() { 1068 | /* implementation details */ 1069 | } 1070 | ``` 1071 | 1072 | ```javascript 1073 | /* recommended */ 1074 | /* customerInfo.directive.js */ 1075 | 1076 | /** 1077 | * @desc sales directive that can be used anywhere across the sales app at a company named Acme 1078 | * @example
1079 | */ 1080 | angular 1081 | .module('sales.widgets') 1082 | .directive('acmeSalesCustomerInfo', salesCustomerInfo); 1083 | 1084 | function salesCustomerInfo() { 1085 | /* implementation details */ 1086 | } 1087 | ``` 1088 | 1089 | ```javascript 1090 | /* recommended */ 1091 | /* spinner.directive.js */ 1092 | 1093 | /** 1094 | * @desc spinner directive that can be used anywhere across apps at a company named Acme 1095 | * @example
1096 | */ 1097 | angular 1098 | .module('shared.widgets') 1099 | .directive('acmeSharedSpinner', sharedSpinner); 1100 | 1101 | function sharedSpinner() { 1102 | /* implementation details */ 1103 | } 1104 | ``` 1105 | 1106 | 注:由于directive使用条件比较广,所以命名就存在很多的选项。选择一个让directive和它的文件名都清楚分明的名字。下面有一些例子,不过更多的建议去看[命名](#命名)章节。 1107 | 1108 | ### 在directive中操作DOM 1109 | ###### [Style [Y072](#style-y072)] 1110 | 1111 | - 当需要直接操作DOM的时候,使用directive。如果有替代方法可以使用,例如:使用CSS来设置样式、[animation services](https://docs.angularjs.org/api/ngAnimate)、Angular模板、[`ngShow`](https://docs.angularjs.org/api/ng/directive/ngShow)或者[`ngHide`](https://docs.angularjs.org/api/ng/directive/ngHide),那么就直接用这些即可。例如,如果一个directive只是想控制显示和隐藏,用ngHide/ngShow即可。 1112 | 1113 | *为什么?*:DOM操作的测试和调试是很困难的,通常会有更好的方法(CSS、animations、templates)。 1114 | 1115 | ### 提供一个唯一的Directive前缀 1116 | ###### [Style [Y073](#style-y073)] 1117 | 1118 | - 提供一个短小、唯一、具有描述性的directive前缀,例如`acmeSalesCustomerInfo`在HTML中声明为`acme-sales-customer-info`。 1119 | 1120 | *为什么?*:方便快速识别directive的内容和起源,例如`acme-`可能预示着这个directive是服务于Acme company。 1121 | 1122 | 注:避免使用`ng-`为前缀,研究一下其它广泛使用的directive避免命名冲突,例如[Ionic Framework](http://ionicframework.com/)的`ion-`。 1123 | 1124 | ### 限制元素和属性 1125 | ###### [Style [Y074](#style-y074)] 1126 | 1127 | - 当创建一个directive需要作为一个独立元素时,restrict值设置为`E`(自定义元素),也可以设置可选值`A`(自定义属性)。一般来说,如果它就是为了独立存在,用`E`是合适的做法。一般原则是允许`EA`,但是当它是独立的时候这更倾向于作为一个元素来实施,当它是为了增强已存在的DOM元素时则更倾向于作为一个属性来实施。 1128 | 1129 | *为什么?*:这很有意义! 1130 | 1131 | *为什么?*:虽然我们允许directive被当作一个class来使用,但如果这个directive的行为确实像一个元素的话,那么把directive当作元素或者属性是更有意义的。 1132 | 1133 | 注:Angular 1.3 +默认使用EA。 1134 | 1135 | ```html 1136 | 1137 |
1138 | ``` 1139 | 1140 | ```javascript 1141 | /* avoid */ 1142 | angular 1143 | .module('app.widgets') 1144 | .directive('myCalendarRange', myCalendarRange); 1145 | 1146 | function myCalendarRange () { 1147 | var directive = { 1148 | link: link, 1149 | templateUrl: '/template/is/located/here.html', 1150 | restrict: 'C' 1151 | }; 1152 | return directive; 1153 | 1154 | function link(scope, element, attrs) { 1155 | /* */ 1156 | } 1157 | } 1158 | ``` 1159 | 1160 | ```html 1161 | 1162 | 1163 |
1164 | ``` 1165 | 1166 | ```javascript 1167 | /* recommended */ 1168 | angular 1169 | .module('app.widgets') 1170 | .directive('myCalendarRange', myCalendarRange); 1171 | 1172 | function myCalendarRange () { 1173 | var directive = { 1174 | link: link, 1175 | templateUrl: '/template/is/located/here.html', 1176 | restrict: 'EA' 1177 | }; 1178 | return directive; 1179 | 1180 | function link(scope, element, attrs) { 1181 | /* */ 1182 | } 1183 | } 1184 | ``` 1185 | 1186 | ### Directives和ControllerAs 1187 | ###### [Style [Y075](#style-y075)] 1188 | 1189 | - directive使用`controller as`语法,和view使用`controller as`保持一致。 1190 | 1191 | *为什么?*:因为不难且有必要这样做。 1192 | 1193 | 注意:下面的directive演示了一些你可以在link和directive控制器中使用scope的方法,用controllerAs。这里把template放在行内是为了在一个地方写出这些代码。 1194 | 1195 | 注意:关于依赖注入的内容,请看[手动依赖注入](#手动依赖注入)。 1196 | 1197 | 注意:directive的控制器是在directive外部的,这种风格避免了由于注入造成的`return`之后的代码无法访问的情况。 1198 | 1199 | ```html 1200 |
1201 | ``` 1202 | 1203 | ```javascript 1204 | angular 1205 | .module('app') 1206 | .directive('myExample', myExample); 1207 | 1208 | function myExample() { 1209 | var directive = { 1210 | restrict: 'EA', 1211 | templateUrl: 'app/feature/example.directive.html', 1212 | scope: { 1213 | max: '=' 1214 | }, 1215 | link: linkFunc, 1216 | controller : ExampleController, 1217 | controllerAs: 'vm', 1218 | bindToController: true // because the scope is isolated 1219 | }; 1220 | 1221 | return directive; 1222 | 1223 | function linkFunc(scope, el, attr, ctrl) { 1224 | console.log('LINK: scope.min = %s *** should be undefined', scope.min); 1225 | console.log('LINK: scope.max = %s *** should be undefined', scope.max); 1226 | console.log('LINK: scope.vm.min = %s', scope.vm.min); 1227 | console.log('LINK: scope.vm.max = %s', scope.vm.max); 1228 | } 1229 | } 1230 | 1231 | ExampleController.$inject = ['$scope']; 1232 | 1233 | function ExampleController($scope) { 1234 | // Injecting $scope just for comparison 1235 | var vm = this; 1236 | 1237 | vm.min = 3; 1238 | 1239 | console.log('CTRL: $scope.vm.min = %s', $scope.vm.min); 1240 | console.log('CTRL: $scope.vm.max = %s', $scope.vm.max); 1241 | console.log('CTRL: vm.min = %s', vm.min); 1242 | console.log('CTRL: vm.max = %s', vm.max); 1243 | } 1244 | ``` 1245 | 1246 | ```html 1247 | 1248 |
hello world
1249 |
max={{vm.max}}
1250 |
min={{vm.min}}
1251 | ``` 1252 | 1253 | 注意:当你把controller注入到link的函数或可访问的directive的attributes时,你可以把它命名为控制器的属性。 1254 | 1255 | ```javascript 1256 | // Alternative to above example 1257 | function linkFunc(scope, el, attr, vm) { // 和上面例子的区别在于把vm直接传递进来 1258 | console.log('LINK: scope.min = %s *** should be undefined', scope.min); 1259 | console.log('LINK: scope.max = %s *** should be undefined', scope.max); 1260 | console.log('LINK: vm.min = %s', vm.min); 1261 | console.log('LINK: vm.max = %s', vm.max); 1262 | } 1263 | ``` 1264 | 1265 | ###### [Style [Y076](#style-y076)] 1266 | 1267 | - 当directive中使用了`controller as`语法时,如果你想把父级作用域绑定到directive的controller作用域时,使用`bindToController = true`。 1268 | 1269 | *为什么?*:这使得把外部作用域绑定到directive controller中变得更加简单。 1270 | 1271 | 注意:Angular 1.3.0才介绍了`bindToController`。 1272 | 1273 | ```html 1274 |
1275 | ``` 1276 | 1277 | ```javascript 1278 | angular 1279 | .module('app') 1280 | .directive('myExample', myExample); 1281 | 1282 | function myExample() { 1283 | var directive = { 1284 | restrict: 'EA', 1285 | templateUrl: 'app/feature/example.directive.html', 1286 | scope: { 1287 | max: '=' 1288 | }, 1289 | controller: ExampleController, 1290 | controllerAs: 'vm', 1291 | bindToController: true 1292 | }; 1293 | 1294 | return directive; 1295 | } 1296 | 1297 | function ExampleController() { 1298 | var vm = this; 1299 | vm.min = 3; 1300 | console.log('CTRL: vm.min = %s', vm.min); 1301 | console.log('CTRL: vm.max = %s', vm.max); 1302 | } 1303 | ``` 1304 | 1305 | ```html 1306 | 1307 |
hello world
1308 |
max={{vm.max}}
1309 |
min={{vm.min}}
1310 | ``` 1311 | 1312 | **[返回顶部](#目录)** 1313 | 1314 | ## 解决Controller的Promises 1315 | ### Controller Activation Promises 1316 | ###### [Style [Y080](#style-y080)] 1317 | 1318 | - 在`activate`函数中解决controller的启动逻辑。 1319 | 1320 | *为什么?*:把启动逻辑放在一个controller中固定的位置可以方便定位、有利于保持测试的一致性,并能够避免controller中到处都是激活逻辑。 1321 | 1322 | *为什么?*:`activate`这个controller使得重用刷新视图的逻辑变得很方便,把所有的逻辑都放到了一起,可以让用户更快地看到视图,可以很轻松地对`ng-view` 或 `ui-view`使用动画,用户体验更好。 1323 | 1324 | 注意:如果你需要在开始使用controller之前有条件地取消路由,那么就用[route resolve](#style-y081)来代替。 1325 | 1326 | ```javascript 1327 | /* avoid */ 1328 | function Avengers(dataservice) { 1329 | var vm = this; 1330 | vm.avengers = []; 1331 | vm.title = 'Avengers'; 1332 | 1333 | dataservice.getAvengers().then(function(data) { 1334 | vm.avengers = data; 1335 | return vm.avengers; 1336 | }); 1337 | } 1338 | ``` 1339 | 1340 | ```javascript 1341 | /* recommended */ 1342 | function Avengers(dataservice) { 1343 | var vm = this; 1344 | vm.avengers = []; 1345 | vm.title = 'Avengers'; 1346 | 1347 | activate(); 1348 | 1349 | //////////// 1350 | 1351 | function activate() { 1352 | return dataservice.getAvengers().then(function(data) { 1353 | vm.avengers = data; 1354 | return vm.avengers; 1355 | }); 1356 | } 1357 | } 1358 | ``` 1359 | 1360 | ### Route Resolve Promises 1361 | ###### [Style [Y081](#style-y081)] 1362 | 1363 | - 当一个controller在激活之前,需要依赖一个promise的完成时,那么就在controller的逻辑执行之前在`$routeProvider`中解决这些依赖。如果你需要在controller被激活之前有条件地取消一个路由,那么就用route resolver。 1364 | 1365 | - 当你决定在过渡到视图之前取消路由时,使用route resolve。 1366 | 1367 | *为什么?*:controller在加载前可能需要一些数据,这些数据可能是从一个通过自定义factory或是[$http](https://docs.angularjs.org/api/ng/service/$http)的promise而来的。[route resolve](https://docs.angularjs.org/api/ngRoute/provider/$routeProvider)允许promise在controller的逻辑执行之前解决,因此它可能对从promise中来的数据做一些处理。 1368 | 1369 | *为什么?*:这段代码将在路由后的controller的激活函数中执行,视图立即加载,promise resolve的时候将会开始进行数据绑定,可以(通过`ng-view`或`ui-view`)在视图的过渡之间加个loading状态的动画。 1370 | 1371 | 注意:这段代码将在路由之前通过一个promise来执行,拒绝了承诺就会取消路由,接受了就会等待路由跳转到新视图。如果你想更快地进入视图,并且无需验证是否可以进入视图,你可以考虑用[控制器 `activate` 技术](#style-y080)。 1372 | 1373 | ```javascript 1374 | /* avoid */ 1375 | angular 1376 | .module('app') 1377 | .controller('Avengers', Avengers); 1378 | 1379 | function Avengers (movieService) { 1380 | var vm = this; 1381 | // unresolved 1382 | vm.movies; 1383 | // resolved asynchronously 1384 | movieService.getMovies().then(function(response) { 1385 | vm.movies = response.movies; 1386 | }); 1387 | } 1388 | ``` 1389 | 1390 | ```javascript 1391 | /* better */ 1392 | 1393 | // route-config.js 1394 | angular 1395 | .module('app') 1396 | .config(config); 1397 | 1398 | function config ($routeProvider) { 1399 | $routeProvider 1400 | .when('/avengers', { 1401 | templateUrl: 'avengers.html', 1402 | controller: 'Avengers', 1403 | controllerAs: 'vm', 1404 | resolve: { 1405 | moviesPrepService: function(movieService) { 1406 | return movieService.getMovies(); 1407 | } 1408 | } 1409 | }); 1410 | } 1411 | 1412 | // avengers.js 1413 | angular 1414 | .module('app') 1415 | .controller('Avengers', Avengers); 1416 | 1417 | Avengers.$inject = ['moviesPrepService']; 1418 | function Avengers (moviesPrepService) { 1419 | var vm = this; 1420 | vm.movies = moviesPrepService.movies; 1421 | } 1422 | ``` 1423 | 1424 | 注意:下面这个例子展示了命名函数的路由解决,这种方式对于调试和处理依赖注入更加方便。 1425 | 1426 | ```javascript 1427 | /* even better */ 1428 | 1429 | // route-config.js 1430 | angular 1431 | .module('app') 1432 | .config(config); 1433 | 1434 | function config($routeProvider) { 1435 | $routeProvider 1436 | .when('/avengers', { 1437 | templateUrl: 'avengers.html', 1438 | controller: 'Avengers', 1439 | controllerAs: 'vm', 1440 | resolve: { 1441 | moviesPrepService: moviesPrepService 1442 | } 1443 | }); 1444 | } 1445 | 1446 | function moviesPrepService(movieService) { 1447 | return movieService.getMovies(); 1448 | } 1449 | 1450 | // avengers.js 1451 | angular 1452 | .module('app') 1453 | .controller('Avengers', Avengers); 1454 | 1455 | Avengers.$inject = ['moviesPrepService']; 1456 | function Avengers(moviesPrepService) { 1457 | var vm = this; 1458 | vm.movies = moviesPrepService.movies; 1459 | } 1460 | ``` 1461 | 注意:示例代码中的`movieService`不符合安全压缩的做法,可以到[手动依赖注入](#手动依赖注入)和[压缩和注释](#压缩和注释)部分学习如何安全压缩。 1462 | 1463 | **[返回顶部](#目录)** 1464 | 1465 | ## 手动依赖注入 1466 | 1467 | ### 压缩的不安全性 1468 | ###### [Style [Y090](#style-y090)] 1469 | 1470 | - 声明依赖时避免使用缩写语法。 1471 | 1472 | *为什么?*:组件的参数(例如controller、factory等等)将会被转换成各种乱七八糟错误的变量。例如,`common`和`dataservice`可能会变成`a`或者`b`,但是这些转换后的变量在Angular中是找不到的。 1473 | 1474 | ```javascript 1475 | /* avoid - not minification-safe*/ 1476 | angular 1477 | .module('app') 1478 | .controller('Dashboard', Dashboard); 1479 | 1480 | function Dashboard(common, dataservice) { 1481 | } 1482 | ``` 1483 | 1484 | 这一段代码在压缩时会产生错误的变量,因此在运行时就会报错。 1485 | 1486 | ```javascript 1487 | /* avoid - not minification-safe*/ 1488 | angular.module('app').controller('Dashboard', d);function d(a, b) { } 1489 | ``` 1490 | 1491 | ### 手动添加依赖 1492 | ###### [Style [Y091](#style-y091)] 1493 | 1494 | - 用`$inject`手动添加Angular组件所需的依赖。 1495 | 1496 | *为什么?*:这种技术反映了使用[`ng-annotate`](https://github.com/olov/ng-annotate)的技术,这就是我推荐的对依赖关系进行自动化创建安全压缩的方式,如果`ng-annotate`检测到已经有了注入,那么它就不会再次重复执行。 1497 | 1498 | *为什么?*:可以避免依赖变成其它Angular找不到的变量,例如,`common`和`dataservice`可能会变成`a`或者`b`。 1499 | 1500 | *为什么?*:避免创建内嵌的依赖,因为一个数组太长不利于阅读,此外,内嵌的方式也会让人感到困惑,比如数组是一系列的字符串,但是最后一个却是组件的function。 1501 | 1502 | ```javascript 1503 | /* avoid */ 1504 | angular 1505 | .module('app') 1506 | .controller('Dashboard', 1507 | ['$location', '$routeParams', 'common', 'dataservice', 1508 | function Dashboard($location, $routeParams, common, dataservice) {} 1509 | ]); 1510 | ``` 1511 | 1512 | ```javascript 1513 | /* avoid */ 1514 | angular 1515 | .module('app') 1516 | .controller('Dashboard', 1517 | ['$location', '$routeParams', 'common', 'dataservice', Dashboard]); 1518 | 1519 | function Dashboard($location, $routeParams, common, dataservice) { 1520 | } 1521 | ``` 1522 | 1523 | ```javascript 1524 | /* recommended */ 1525 | angular 1526 | .module('app') 1527 | .controller('Dashboard', Dashboard); 1528 | 1529 | Dashboard.$inject = ['$location', '$routeParams', 'common', 'dataservice']; 1530 | 1531 | function Dashboard($location, $routeParams, common, dataservice) { 1532 | } 1533 | ``` 1534 | 1535 | 注意:当你的函数处于return语句后面,那么`$inject`是无法访问的(这会在directive中发生),你可以通过把Controller移到directive外面来解决这个问题。 1536 | 1537 | ```javascript 1538 | /* avoid */ 1539 | // inside a directive definition 1540 | function outer() { 1541 | var ddo = { 1542 | controller: DashboardPanelController, 1543 | controllerAs: 'vm' 1544 | }; 1545 | return ddo; 1546 | 1547 | DashboardPanelController.$inject = ['logger']; // Unreachable 1548 | function DashboardPanelController(logger) { 1549 | } 1550 | } 1551 | ``` 1552 | 1553 | ```javascript 1554 | /* recommended */ 1555 | // outside a directive definition 1556 | function outer() { 1557 | var ddo = { 1558 | controller: DashboardPanelController, 1559 | controllerAs: 'vm' 1560 | }; 1561 | return ddo; 1562 | } 1563 | 1564 | DashboardPanelController.$inject = ['logger']; 1565 | function DashboardPanelController(logger) { 1566 | } 1567 | ``` 1568 | 1569 | ### 手动确定路由解析器依赖 1570 | ###### [Style [Y092](#style-y092)] 1571 | 1572 | - 用`$inject`手动给Angular组件添加路由解析器依赖。 1573 | 1574 | *为什么?*:这种技术打破了路由解析的匿名函数的形式,易读。 1575 | 1576 | *为什么?*:`$inject`语句可以让任何依赖都可以安全压缩。 1577 | 1578 | ```javascript 1579 | /* recommended */ 1580 | function config ($routeProvider) { 1581 | $routeProvider 1582 | .when('/avengers', { 1583 | templateUrl: 'avengers.html', 1584 | controller: 'AvengersController', 1585 | controllerAs: 'vm', 1586 | resolve: { 1587 | moviesPrepService: moviesPrepService 1588 | } 1589 | }); 1590 | } 1591 | 1592 | moviesPrepService.$inject = ['movieService']; 1593 | function moviesPrepService(movieService) { 1594 | return movieService.getMovies(); 1595 | } 1596 | ``` 1597 | 1598 | **[返回顶部](#目录)** 1599 | 1600 | ## 压缩和注释 1601 | 1602 | ### ng-annotate 1603 | ###### [Style [Y100](#style-y100)] 1604 | 1605 | - 在[Gulp](http://gulpjs.com)或[Grunt](http://gruntjs.com)中使用[ng-annotate](//github.com/olov/ng-annotate),用`/** @ngInject */`对需要自动依赖注入的function进行注释。 1606 | 1607 | *为什么?*:可以避免代码中的依赖使用到任何不安全的写法。 1608 | 1609 | *为什么?*:不推荐用[`ng-min`](https://github.com/btford/ngmin)。 1610 | 1611 | >我更喜欢Gulp,因为我觉得它是易写易读易调试的。 1612 | 1613 | 下面的代码没有注入依赖,显然压缩是不安全的。 1614 | 1615 | ```javascript 1616 | angular 1617 | .module('app') 1618 | .controller('Avengers', Avengers); 1619 | 1620 | /* @ngInject */ 1621 | function Avengers (storageService, avengerService) { 1622 | var vm = this; 1623 | vm.heroSearch = ''; 1624 | vm.storeHero = storeHero; 1625 | 1626 | function storeHero(){ 1627 | var hero = avengerService.find(vm.heroSearch); 1628 | storageService.save(hero.name, hero); 1629 | } 1630 | } 1631 | ``` 1632 | 1633 | 当上面的代码通过ng-annotate运行时,就会产生如下的带有`$inject`注释的输出结果,这样的话压缩就会安全了。 1634 | 1635 | ```javascript 1636 | angular 1637 | .module('app') 1638 | .controller('Avengers', Avengers); 1639 | 1640 | /* @ngInject */ 1641 | function Avengers (storageService, avengerService) { 1642 | var vm = this; 1643 | vm.heroSearch = ''; 1644 | vm.storeHero = storeHero; 1645 | 1646 | function storeHero(){ 1647 | var hero = avengerService.find(vm.heroSearch); 1648 | storageService.save(hero.name, hero); 1649 | } 1650 | } 1651 | 1652 | Avengers.$inject = ['storageService', 'avengerService']; 1653 | ``` 1654 | 1655 | 注意:如果`ng-annotate`检测到已经有注入了(例如发现了`@ngInject`),就不会重复生成`$inject`代码了。 1656 | 1657 | 注意:路由的函数前面也可以用`/* @ngInject */` 1658 | 1659 | ```javascript 1660 | // Using @ngInject annotations 1661 | function config($routeProvider) { 1662 | $routeProvider 1663 | .when('/avengers', { 1664 | templateUrl: 'avengers.html', 1665 | controller: 'Avengers', 1666 | controllerAs: 'vm', 1667 | resolve: { /* @ngInject */ 1668 | moviesPrepService: function(movieService) { 1669 | return movieService.getMovies(); 1670 | } 1671 | } 1672 | }); 1673 | } 1674 | ``` 1675 | 1676 | > 注意:从Angular 1.3开始,你就可以用[`ngApp`](https://docs.angularjs.org/api/ng/directive/ngApp)指令的 `ngStrictDi`参数来检测任何可能失去依赖的地方,当以“strict-di”模式创建injector时,会导致应用程序无法调用不使用显示函数注释的函数(这也许无法安全压缩)。记录在控制台的调试信息可以帮助追踪出问题的代码。我只在需要调试的时候才会用到`ng-strict-di`。 1677 | `` 1678 | 1679 | ### 使用Gulp或Grunt结合ng-annotate 1680 | ###### [Style [Y101](#style-y101)] 1681 | 1682 | - 在自动化任务中使用[gulp-ng-annotate](https://www.npmjs.org/package/gulp-ng-annotate)或[grunt-ng-annotate](https://www.npmjs.org/package/grunt-ng-annotate),把`/* @ngInject */`注入到任何有依赖关系函数的前面。 1683 | 1684 | *为什么?*:ng-annotate会捕获大部分的依赖关系,但是有时候需要借助于`/* @ngInject */`语法提示。 1685 | 1686 | 下面的代码是gulp任务使用ngAnnotate的例子。 1687 | 1688 | ```javascript 1689 | gulp.task('js', ['jshint'], function() { 1690 | var source = pkg.paths.js; 1691 | 1692 | return gulp.src(source) 1693 | .pipe(sourcemaps.init()) 1694 | .pipe(concat('all.min.js', {newLine: ';'})) 1695 | // Annotate before uglify so the code get's min'd properly. 1696 | .pipe(ngAnnotate({ 1697 | // true helps add where @ngInject is not used. It infers. 1698 | // Doesn't work with resolve, so we must be explicit there 1699 | add: true 1700 | })) 1701 | .pipe(bytediff.start()) 1702 | .pipe(uglify({mangle: true})) 1703 | .pipe(bytediff.stop()) 1704 | .pipe(sourcemaps.write('./')) 1705 | .pipe(gulp.dest(pkg.paths.dev)); 1706 | }); 1707 | ``` 1708 | 1709 | **[返回顶部](#目录)** 1710 | 1711 | ## 异常处理 1712 | 1713 | ### 修饰符 1714 | ###### [Style [Y110](#style-y110)] 1715 | 1716 | - 使用一个[decorator](https://docs.angularjs.org/api/auto/service/$provide#decorator),在配置的时候用[`$provide`](https://docs.angularjs.org/api/auto/service/$provide)服务,当发生异常时,在[`$exceptionHandler`](https://docs.angularjs.org/api/ng/service/$exceptionHandler)服务中执行自定义的处理方法。 1717 | 1718 | *为什么?*:在开发时和运行时提供了一种统一的方式来处理未被捕获的Angular异常。 1719 | 1720 | 注:另一个选项是用来覆盖service的,这个可以代替decorator,这是一个非常nice的选项,但是如果你想保持默认行为,那么推荐你扩展一个decorator。 1721 | 1722 | ```javascript 1723 | /* recommended */ 1724 | angular 1725 | .module('blocks.exception') 1726 | .config(exceptionConfig); 1727 | 1728 | exceptionConfig.$inject = ['$provide']; 1729 | 1730 | function exceptionConfig($provide) { 1731 | $provide.decorator('$exceptionHandler', extendExceptionHandler); 1732 | } 1733 | 1734 | extendExceptionHandler.$inject = ['$delegate', 'toastr']; 1735 | 1736 | function extendExceptionHandler($delegate, toastr) { 1737 | return function(exception, cause) { 1738 | $delegate(exception, cause); 1739 | var errorData = { 1740 | exception: exception, 1741 | cause: cause 1742 | }; 1743 | /** 1744 | * Could add the error to a service's collection, 1745 | * add errors to $rootScope, log errors to remote web server, 1746 | * or log locally. Or throw hard. It is entirely up to you. 1747 | * throw exception; 1748 | */ 1749 | toastr.error(exception.msg, errorData); 1750 | }; 1751 | } 1752 | ``` 1753 | 1754 | ### 异常捕获器 1755 | ###### [Style [Y111](#style-y111)] 1756 | 1757 | - 创建一个暴露了一个接口的factory来捕获异常并以合适方式处理异常。 1758 | 1759 | *为什么?*:提供了一个统一的方法来捕获代码中抛出的异常。 1760 | 1761 | 注:异常捕获器对特殊异常的捕获和反应是非常友好的,例如,使用XHR从远程服务获取数据时,你想要捕获所有异常并做出不同的反应。 1762 | 1763 | ```javascript 1764 | /* recommended */ 1765 | angular 1766 | .module('blocks.exception') 1767 | .factory('exception', exception); 1768 | 1769 | exception.$inject = ['logger']; 1770 | 1771 | function exception(logger) { 1772 | var service = { 1773 | catcher: catcher 1774 | }; 1775 | return service; 1776 | 1777 | function catcher(message) { 1778 | return function(reason) { 1779 | logger.error(message, reason); 1780 | }; 1781 | } 1782 | } 1783 | ``` 1784 | 1785 | ### 路由错误 1786 | ###### [Style [Y112](#style-y112)] 1787 | 1788 | - 用[`$routeChangeError`](https://docs.angularjs.org/api/ngRoute/service/$route#$routeChangeError)来处理并打印出所有的路由错误信息。 1789 | 1790 | *为什么?*:提供一个统一的方式来处理所有的路由错误。 1791 | 1792 | *为什么?*:当一个路由发生错误的时候,可以给展示一个提示信息,提高用户体验。 1793 | 1794 | ```javascript 1795 | /* recommended */ 1796 | var handlingRouteChangeError = false; 1797 | 1798 | function handleRoutingErrors() { 1799 | /** 1800 | * Route cancellation: 1801 | * On routing error, go to the dashboard. 1802 | * Provide an exit clause if it tries to do it twice. 1803 | */ 1804 | $rootScope.$on('$routeChangeError', 1805 | function(event, current, previous, rejection) { 1806 | if (handlingRouteChangeError) { return; } 1807 | handlingRouteChangeError = true; 1808 | var destination = (current && (current.title || 1809 | current.name || current.loadedTemplateUrl)) || 1810 | 'unknown target'; 1811 | var msg = 'Error routing to ' + destination + '. ' + 1812 | (rejection.msg || ''); 1813 | 1814 | /** 1815 | * Optionally log using a custom service or $log. 1816 | * (Don't forget to inject custom service) 1817 | */ 1818 | logger.warning(msg, [current]); 1819 | 1820 | /** 1821 | * On routing error, go to another route/state. 1822 | */ 1823 | $location.path('/'); 1824 | 1825 | } 1826 | ); 1827 | } 1828 | ``` 1829 | 1830 | **[返回顶部](#目录)** 1831 | 1832 | ## 命名 1833 | 1834 | ### 命名原则 1835 | ###### [Style [Y120](#style-y120)] 1836 | 1837 | - 遵循以描述组件功能,然后是类型(可选)的方式来给所有的组件提供统一的命名,我推荐的做法是`feature.type.js`。大多数文件都有2个名字。 1838 | * 文件名 (`avengers.controller.js`) 1839 | * 带有Angular的注册组件名 (`AvengersController`) 1840 | 1841 | *为什么?*:命名约定有助于为一目了然地找到内容提供一个统一的方式,在项目中和团队中保持统一性是非常重要的,保持统一性对于跨公司来说提供了巨大的效率。 1842 | 1843 | *为什么?*:命名约定应该只为代码的检索和沟通提供方便。 1844 | 1845 | ### 功能文件命名 1846 | ###### [Style [Y121](#style-y121)] 1847 | 1848 | - 遵循以“描述组件功能.类型(可选)”的方式来给所有的组件提供统一的命名,我推荐的做法是`feature.type.js`。 1849 | 1850 | *为什么?*:为快速识别组件提供了统一的方式。 1851 | 1852 | *为什么?*:为任何自动化的任务提供模式匹配。 1853 | 1854 | ```javascript 1855 | /** 1856 | * common options 1857 | */ 1858 | 1859 | // Controllers 1860 | avengers.js 1861 | avengers.controller.js 1862 | avengersController.js 1863 | 1864 | // Services/Factories 1865 | logger.js 1866 | logger.service.js 1867 | loggerService.js 1868 | ``` 1869 | 1870 | ```javascript 1871 | /** 1872 | * recommended 1873 | */ 1874 | 1875 | // controllers 1876 | avengers.controller.js 1877 | avengers.controller.spec.js 1878 | 1879 | // services/factories 1880 | logger.service.js 1881 | logger.service.spec.js 1882 | 1883 | // constants 1884 | constants.js 1885 | 1886 | // module definition 1887 | avengers.module.js 1888 | 1889 | // routes 1890 | avengers.routes.js 1891 | avengers.routes.spec.js 1892 | 1893 | // configuration 1894 | avengers.config.js 1895 | 1896 | // directives 1897 | avenger-profile.directive.js 1898 | avenger-profile.directive.spec.js 1899 | ``` 1900 | 1901 | 注意:另外一种常见的约定就是不要用`controller`这个词来给controller文件命名,例如不要用`avengers.controller.js`,而是用`avengers.js`。所有其它的约定都坚持使用类型作为后缀,但是controller是组件中最为常用的类型,因此这种做法的好处貌似仅仅是节省了打字,但是仍然很容易识别。我建议你为你的团队选择一种约定,并且要保持统一性。我喜欢的命名方式是`avengers.controller.js`。 1902 | 1903 | ```javascript 1904 | /** 1905 | * recommended 1906 | */ 1907 | // Controllers 1908 | avengers.js 1909 | avengers.spec.js 1910 | ``` 1911 | 1912 | ### 测试文件命名 1913 | ###### [Style [Y122](#style-y122)] 1914 | 1915 | - 和组件命名差不多,带上一个`spec`后缀。 1916 | 1917 | *为什么?*:为快速识别组件提供统一的方式。 1918 | 1919 | *为什么?*:为[karma](http://karma-runner.github.io/)或是其它测试运行器提供模式匹配。 1920 | 1921 | ```javascript 1922 | /** 1923 | * recommended 1924 | */ 1925 | avengers.controller.spec.js 1926 | logger.service.spec.js 1927 | avengers.routes.spec.js 1928 | avenger-profile.directive.spec.js 1929 | ``` 1930 | 1931 | ### Controller命名 1932 | ###### [Style [Y123](#style-y123)] 1933 | 1934 | - 为所有controller提供统一的名称,先特征后名字,鉴于controller是构造函数,所以要采用UpperCamelCase(每个单词首字母大写)的方式。 1935 | 1936 | *为什么?*:为快速识别和引用controller提供统一的方式。 1937 | 1938 | *为什么?*:UpperCamelCase是常规的识别一个可以用构造函数来实例化的对象的方式。 1939 | 1940 | ```javascript 1941 | /** 1942 | * recommended 1943 | */ 1944 | 1945 | // avengers.controller.js 1946 | angular 1947 | .module 1948 | .controller('HeroAvengersController', HeroAvengersController); 1949 | 1950 | function HeroAvengers(){ } 1951 | ``` 1952 | 1953 | ### Controller命名后缀 1954 | ###### [Style [Y124](#style-y124)] 1955 | 1956 | - 使用`Controller`。 1957 | 1958 | *为什么?*:`Controller`使用更广泛、更明确、更具有描述性。 1959 | 1960 | ```javascript 1961 | /** 1962 | * recommended 1963 | */ 1964 | 1965 | // avengers.controller.js 1966 | angular 1967 | .module 1968 | .controller('AvengersController', AvengersController); 1969 | 1970 | function AvengersController(){ } 1971 | ``` 1972 | 1973 | ### Factory命名 1974 | ###### [Style [Y125](#style-y125)] 1975 | 1976 | - 一样要统一,对service和factory使用camel-casing(驼峰式,第一个单词首字母小写,后面单词首字母大写)方式。避免使用`$`前缀。 1977 | 1978 | *为什么?*:可以快速识别和引用factory。 1979 | 1980 | *为什么?*:避免与内部使用`$`前缀的服务发生冲突。 1981 | 1982 | ```javascript 1983 | /** 1984 | * recommended 1985 | */ 1986 | 1987 | // logger.service.js 1988 | angular 1989 | .module 1990 | .factory('logger', logger); 1991 | 1992 | function logger(){ } 1993 | ``` 1994 | 1995 | ### Directive组件命名 1996 | ###### [Style [Y126](#style-y126)] 1997 | 1998 | - 使用camel-case方式,用一个短的前缀来描述directive在哪个区域使用(一些例子中是使用公司前缀或是项目前缀)。 1999 | 2000 | *为什么?*:可以快速识别和引用controller。 2001 | 2002 | ```javascript 2003 | /** 2004 | * recommended 2005 | */ 2006 | 2007 | // avenger-profile.directive.js 2008 | angular 2009 | .module 2010 | .directive('xxAvengerProfile', xxAvengerProfile); 2011 | 2012 | // usage is 2013 | 2014 | function xxAvengerProfile(){ } 2015 | ``` 2016 | 2017 | ### 模块 2018 | ###### [Style [Y127](#style-y127)] 2019 | 2020 | - 当有很多的模块时,主模块文件命名成`app.module.js`,其它依赖模块以它们代表的内容来命名。例如,一个管理员模块命名成`admin.module.js`,它们各自的注册模块名字就是`app`和`admin`。 2021 | 2022 | *为什么?*:给多模块的应用提供统一的方式,这也是为了扩展大型应用。 2023 | 2024 | *为什么?*:对使用任务来自动化加载所有模块的定义(先)和其它所有的angular文件(后)提供了一种简单的方式。 2025 | 2026 | ### 配置 2027 | ###### [Style [Y128](#style-y128)] 2028 | 2029 | - 把一个模块的配置独立到它自己的文件中,以这个模块为基础命名。`app`模块的配置文件命名成`app.config.js`(或是`config.js`),`admin.module.js`的配置文件命名成`admin.config.js`。 2030 | 2031 | *为什么?*:把配置从模块定义、组件和活跃代码中分离出来。 2032 | 2033 | *为什么?*:为设置模块的配置提供了一个可识别的地方。 2034 | 2035 | ### 路由 2036 | ###### [Style [Y129](#style-y129)] 2037 | 2038 | - 把路由的配置独立到单独的文件。主模块的路由可能是`app.route.js`,`admin`模块的路由可能是`admin.route.js`。即使是在很小的应用中,我也喜欢把路由的配置从其余的配置中分离出来。 2039 | 2040 | **[返回顶部](#目录)** 2041 | 2042 | ## 应用程序结构的LIFT准则 2043 | ### LIFT 2044 | ###### [Style [Y140](#style-y140)] 2045 | 2046 | - 构建一个可以快速定位(`L`ocate)代码、一目了然地识别(`I`dentify)代码、拥有一个平直(`F`lattest)的结构、尽量(`T`ry)坚持DRY(Don’t Repeat Yourself)的应用程序,其结构应该遵循这4项基本准则。 2047 | 2048 | *为什么是LIFT?*: 提供一个有良好扩展的结构,并且是模块化的,更快的找到代码能够帮助开发者提高效率。另一种检查你的app结构的方法就是问你自己:你能多块地打开涉及到一个功能的所有相关文件并开始工作? 2049 | 2050 | 当我发现我的的代码结构很恶心的时候,我就重新看看LIFT准则。 2051 | 2052 | 1. 轻松定位代码(L) 2053 | 2. 一眼识别代码(I) 2054 | 3. 平直的代码结构(层级不要太多)(F) 2055 | 4. 尽量保持不要写重复代码(T) 2056 | 2057 | ### Locate 2058 | ###### [Style [Y141](#style-y141)] 2059 | 2060 | - 更直观、更简单、更快捷地定位代码 2061 | 2062 | *为什么?*:我发现这对于一个项目是非常重要的,如果一个团队不能快速找到他们需要工作的文件,这将不能使团队足够高效地工作,那么这个代码结构就得改变。你可能不知道文件名或是相关的文件放在了哪里,那么就把他们放在最直观的地方,放在一起会节省大量的时间。下面是一个参考目录结构。 2063 | 2064 | ``` 2065 | /bower_components 2066 | /client 2067 | /app 2068 | /avengers 2069 | /blocks 2070 | /exception 2071 | /logger 2072 | /core 2073 | /dashboard 2074 | /data 2075 | /layout 2076 | /widgets 2077 | /content 2078 | index.html 2079 | .bower.json 2080 | ``` 2081 | 2082 | ### Identify 2083 | ###### [Style [Y142](#style-y142)] 2084 | 2085 | - 当你看到一个文件时你应该能够立即知道它包含了什么、代表了什么。 2086 | 2087 | *为什么?*:你花费更少的时间来了解代码代表了什么,并且变得更加高效。如果这意味着你需要更长的名字,那么就这么干吧。文件名一定要具有描述性,保持和文件内容互为一体。避免文件中有多个controller,多个service,甚至是混合的。 2088 | 2089 | ### Flat 2090 | ###### [Style [Y143](#style-y143)] 2091 | 2092 | - 尽可能长时间地保持一个平直的文件夹结构,如果你的文件夹层级超过7+,那么就开始考虑分离。 2093 | 2094 | *为什么?*:没有谁想在一个7级文件夹中寻找一个文件,你可以考虑一下网页导航栏有那么多层。文件夹结构没有硬性规则,但是当一个文件夹下的文件有7-10个,那么就是时候创建子文件夹了,文件夹的层级一定要把握好。一直使用一个平直的结构,直到确实有必要(帮助其它的LIFT)创建一个新的文件夹。 2095 | 2096 | ### T-DRY(尽量坚持DRY) 2097 | ###### [Style [Y144](#style-y144)] 2098 | 2099 | - 坚持DRY,但是不要疯了一样的做却牺牲了可读性。 2100 | 2101 | *为什么?*:保持DRY很重要,但是如果牺牲了其它LIFT,那么它就没那么重要了,这就是为什么说尽量坚持DRY。 2102 | 2103 | **[返回顶部](#目录)** 2104 | 2105 | ## 应用程序结构 2106 | 2107 | ### 总规范 2108 | ###### [Style [Y150](#style-y150)] 2109 | 2110 | - 有实施的短期看法和长远的目标,换句话说,从小处入手,但是要记住app的走向。app的所有代码都在一个叫做`app`的根目录下,所有的内容都遵循一个功能一个文件,每一个controller、service、module、view都是独立的文件。第三方脚本存放在另外的根文件夹中(`bower_components`、`scripts`、`lib`)。 2111 | 2112 | 注:了解实例结构的具体信息看[Angular应用结构](http://www.johnpapa.net/angular-app-structuring-guidelines/)。 2113 | 2114 | ### Layout 2115 | ###### [Style [Y151](#style-y151)] 2116 | 2117 | - 把定义应用程序总体布局的组件放到`layout`文件夹中,如导航、内容区等等。 2118 | 2119 | *为什么?*:复用。 2120 | 2121 | ### 按功能划分文件夹结构 2122 | ###### [Style [Y152](#style-y152)] 2123 | 2124 | - 按照它们代表的功能来给创建的文件夹命名,当文件夹包含的文件超过7个(根据需要自行调整数量限制),就考虑新建文件夹。 2125 | 2126 | *为什么?*:开发者可以快速定位代码、快速识别文件代表的意思,结构尽可能平直,没有重复,没有多余名字。 2127 | 2128 | *为什么?*:LIFT规范都包括在内。 2129 | 2130 | *为什么?*:通过组织内容和让它们保持和LIFT指导准则一致,帮助降低应用程序变得混乱的可能性。 2131 | 2132 | *为什么?*:超过10个文件时,在一个一致性的文件夹中很容易定位,但是在一个平直的文件夹结构中确实很难定位。 2133 | 2134 | ```javascript 2135 | /** 2136 | * recommended 2137 | */ 2138 | 2139 | app/ 2140 | app.module.js 2141 | app.config.js 2142 | directives/ 2143 | calendar.directive.js 2144 | calendar.directive.html 2145 | user-profile.directive.js 2146 | user-profile.directive.html 2147 | services/ 2148 | dataservice.js 2149 | localstorage.js 2150 | logger.js 2151 | spinner.js 2152 | layout/ 2153 | shell.html 2154 | shell.controller.js 2155 | topnav.html 2156 | topnav.controller.js 2157 | people/ 2158 | attendees.html 2159 | attendees.controller.js 2160 | people.routes.js 2161 | speakers.html 2162 | speakers.controller.js 2163 | speaker-detail.html 2164 | speaker-detail.controller.js 2165 | sessions/ 2166 | sessions.html 2167 | sessions.controller.js 2168 | sessions.routes.js 2169 | session-detail.html 2170 | session-detail.controller.js 2171 | ``` 2172 | 2173 | ![实例App结构](https://raw.githubusercontent.com/johnpapa/angular-styleguide/master/a1/assets/modularity-2.png) 2174 | 2175 | 注意:不要使用按类型划分文件夹结构,因为如果这样的话,当做一个功能时,需要在多个文件夹中来回切换。当应用程序有5个、10个,甚至是25个以上的view、controller(或其他feature)时,这种方式将迅速变得不实用,这就使得它定位文件比按功能分文件夹的方式要困难的多。 2176 | 2177 | ```javascript 2178 | /* 2179 | * avoid 2180 | * Alternative folders-by-type. 2181 | * I recommend "folders-by-feature", instead. 2182 | */ 2183 | 2184 | app/ 2185 | app.module.js 2186 | app.config.js 2187 | app.routes.js 2188 | directives.js 2189 | controllers/ 2190 | attendees.js 2191 | session-detail.js 2192 | sessions.js 2193 | shell.js 2194 | speakers.js 2195 | speaker-detail.js 2196 | topnav.js 2197 | directives/ 2198 | calendar.directive.js 2199 | calendar.directive.html 2200 | user-profile.directive.js 2201 | user-profile.directive.html 2202 | services/ 2203 | dataservice.js 2204 | localstorage.js 2205 | logger.js 2206 | spinner.js 2207 | views/ 2208 | attendees.html 2209 | session-detail.html 2210 | sessions.html 2211 | shell.html 2212 | speakers.html 2213 | speaker-detail.html 2214 | topnav.html 2215 | ``` 2216 | 2217 | **[返回顶部](#目录)** 2218 | 2219 | ## 模块化 2220 | 2221 | ### 许多小的、独立的模块 2222 | ###### [Style [Y160](#style-y160)] 2223 | 2224 | - 创建只封装一个职责的小模块。 2225 | 2226 | *为什么?*:模块化的应用程序很容易添加新的功能。 2227 | 2228 | ### 创建一个App Module 2229 | ###### [Style [Y161](#style-y161)] 2230 | 2231 | - 创建一个应用程序的根模块,它的职责是把应用程序中所有的模块和功能都放到一起。 2232 | 2233 | *为什么?*:Angular鼓励模块化和分离模式。创建根模块的作用是把其它模块都绑定到一起,这为增加或是删除一个模块提供了非常简单的方法。 2234 | 2235 | *为什么?*:应用程序模块变成了一个描述哪些模块有助于定义应用程序的清单。 2236 | 2237 | ### 保持App Module的精简 2238 | ###### [Style [Y162](#style-y162)] 2239 | 2240 | - app module中只放聚合其它模块的逻辑,具体功能在它们自己的模块中实现。 2241 | 2242 | *为什么?*:添加额外的代码(获取数据、展现视图、其它和聚合模块无关的代码)到app module中使app module变得很糟糕,也使得模块难以重用和关闭。 2243 | 2244 | ### 功能区域就是模块 2245 | ###### [Style [Y163](#style-y163)] 2246 | 2247 | - 创建代表功能区的模块,例如布局、可重用、共享服务、仪表盘和app的特殊功能(例如客户、管理、销售)。 2248 | 2249 | *为什么?*:自包含的模块可以无缝地被添加到应用程序中。 2250 | 2251 | *为什么?*:项目进行功能迭代时,可以专注于功能,在开发完成启用它们即可。 2252 | 2253 | *为什么?*:把功能拆分成不同模块方便测试。 2254 | 2255 | ### 可重用的块就是模块 2256 | ###### [Style [Y164](#style-y164)] 2257 | 2258 | - 为通用service创建代表可重用的应用程序块的模块,例如异常处理、日志记录、诊断、安全性和本地数据储藏等模块。 2259 | 2260 | *为什么?*:这些类型的功能在很多应用程序中都需要用到,所以把它们分离到自己的模块中,它们可以变成通用的应用程序,也能被跨应用地进行重用。 2261 | 2262 | ### 模块依赖 2263 | ###### [Style [Y165](#style-y165)] 2264 | 2265 | - 应用程序根模块依赖于应用程序特定的功能模块、共享的和可复用的模块。 2266 | 2267 | ![模块化和依赖](https://raw.githubusercontent.com/johnpapa/angular-styleguide/master/a1/assets/modularity-1.png) 2268 | 2269 | *为什么?*:主程序模块包含一个能快速识别应用程序功能的清单。 2270 | 2271 | *为什么?*:每个功能区都包含一个它依赖了哪些模块的列表,因此其它应用可以把它当作一个依赖引入进来。 2272 | 2273 | *为什么?*:程序内部的功能,如共享数据的服务变得容易定位,并且从`app.core`中共享。 2274 | 2275 | 注意:这是保持一致性的一种策略,这里有很多不错的选择,选择一种统一的,遵循Angular依赖规则,这将易于维护和扩展。 2276 | 2277 | > 我的不同项目间的结构略有不同,但是它们都遵循了这些结构和模块化的准则,具体的实施方案会根据功能和团队发生变化。也就是说,不要在一棵树上吊死,但是心中一定要记得保持一致性、可维护性和效率。 2278 | 2279 | > 小项目中,你可以直接把所有依赖都放到app module中,这对于小项目来说比较容易维护,但是想在此项目外重用模块就比较难了。 2280 | 2281 | **[返回顶部](#目录)** 2282 | 2283 | ## 启动逻辑 2284 | 2285 | ### 配置 2286 | ###### [Style [Y170](#style-y170)] 2287 | 2288 | - 必须在angular应用启动前进行配置才能把代码注入到[模块配置](https://docs.angularjs.org/guide/module#module-loading-dependencies),理想的一些case应该包括providers和constants。 2289 | 2290 | *为什么?*:这使得在更少的地方进行配置变得容易。 2291 | 2292 | ```javascript 2293 | angular 2294 | .module('app') 2295 | .config(configure); 2296 | 2297 | configure.$inject = 2298 | ['routerHelperProvider', 'exceptionHandlerProvider', 'toastr']; 2299 | 2300 | function configure (routerHelperProvider, exceptionHandlerProvider, toastr) { 2301 | exceptionHandlerProvider.configure(config.appErrorPrefix); 2302 | configureStateHelper(); 2303 | 2304 | toastr.options.timeOut = 4000; 2305 | toastr.options.positionClass = 'toast-bottom-right'; 2306 | 2307 | //////////////// 2308 | 2309 | function configureStateHelper() { 2310 | routerHelperProvider.configure({ 2311 | docTitle: 'NG-Modular: ' 2312 | }); 2313 | } 2314 | } 2315 | ``` 2316 | 2317 | ### 运行代码块 2318 | ###### [Style [Y171](#style-y171)] 2319 | 2320 | - 任何在应用程序启动时需要运行的代码都应该在factory中声明,通过一个function暴露出来,然后注入到[运行代码块](https://docs.angularjs.org/guide/module#module-loading-dependencies)中。 2321 | 2322 | *为什么?*:直接在运行代码块处写代码将会使得测试变得很困难,相反,如果放到facotry则会使的抽象和模拟变得很简单。 2323 | 2324 | ```javascript 2325 | angular 2326 | .module('app') 2327 | .run(runBlock); 2328 | 2329 | runBlock.$inject = ['authenticator', 'translator']; 2330 | 2331 | function runBlock(authenticator, translator) { 2332 | authenticator.initialize(); 2333 | translator.initialize(); 2334 | } 2335 | ``` 2336 | 2337 | **[返回顶部](#目录)** 2338 | 2339 | ## Angular $包装服务 2340 | 2341 | ### $document和$window 2342 | ###### [Style [Y180](#style-y180)] 2343 | 2344 | - 用[`$document`](https://docs.angularjs.org/api/ng/service/$document)和[`$window`](https://docs.angularjs.org/api/ng/service/$window)代替`document`和`window`。 2345 | 2346 | *为什么?*:使用内部包装服务将更容易测试,也避免了你自己去模拟document和window。 2347 | 2348 | ### $timeout和$interval 2349 | ###### [Style [Y181](#style-y181)] 2350 | 2351 | - 用[`$timeout`](https://docs.angularjs.org/api/ng/service/$timeout)和[`$interval`](https://docs.angularjs.org/api/ng/service/$interval)代替`setTimeout`和`setInterval` 。 2352 | 2353 | *为什么?*:易于测试,处理Angular消化周期从而保证数据的同步绑定。 2354 | 2355 | **[返回顶部](#目录)** 2356 | 2357 | ## 测试 2358 | 单元测试有助于保持代码的清晰,因此我加入一些关于单元测试的基础和获取更多信息的链接。 2359 | 2360 | ### 用故事来编写测试 2361 | ###### [Style [Y190](#style-y190)] 2362 | 2363 | - 给每一个故事都写一组测试,先创建一个空的测试,然后用你给这个故事写的代码来填充它。 2364 | 2365 | *为什么?*:编写测试有助于明确规定你的故事要做什么、不做什么以及你如何判断是否成功。 2366 | 2367 | ```javascript 2368 | it('should have Avengers controller', function() { 2369 | //TODO 2370 | }); 2371 | 2372 | it('should find 1 Avenger when filtered by name', function() { 2373 | //TODO 2374 | }); 2375 | 2376 | it('should have 10 Avengers', function() { 2377 | //TODO (mock data?) 2378 | }); 2379 | 2380 | it('should return Avengers via XHR', function() { 2381 | //TODO ($httpBackend?) 2382 | }); 2383 | 2384 | // and so on 2385 | ``` 2386 | 2387 | ### 测试库 2388 | ###### [Style [Y191](#style-y191)] 2389 | 2390 | - 用[Jasmine](http://jasmine.github.io/)或者[Mocha](http://mochajs.org)进行单元测试。 2391 | 2392 | *为什么?*:Angular社区中Jasmine和Mocha都用的很广,两者都很稳定,可维护性好,提供强大的测试功能。 2393 | 2394 | 注意:使用Mocha时你可以考虑选择一个类似[Chai](http://chaijs.com)的提示库。 2395 | 2396 | ### 测试运行器 2397 | ###### [Style [Y192](#style-y192)] 2398 | 2399 | - [Karma](http://karma-runner.github.io)。 2400 | 2401 | *为什么?*:Karma容易配置,代码发生修改时自动运行。 2402 | 2403 | *为什么?*:可以通过自身或是Grunt、Gulp方便地钩入持续集成的进程。 2404 | 2405 | *为什么?*:一些IDE已经开始集成Karma了,如[WebStorm](http://www.jetbrains.com/webstorm/)和[Visual Studio](http://visualstudiogallery.msdn.microsoft.com/02f47876-0e7a-4f6c-93f8-1af5d5189225)。 2406 | 2407 | *为什么?*:Karma可以很好的和自动化任务工具如[Grunt](http://www.gruntjs.com)(带有[grunt-karma](https://github.com/karma-runner/grunt-karma))和[Gulp](http://www.gulpjs.com)(带有[gulp-karma](https://github.com/lazd/gulp-karma))合作。 2408 | 2409 | ### Stubbing和Spying 2410 | ###### [Style [Y193](#style-y193)] 2411 | 2412 | - 用[Sinon](http://sinonjs.org/)。 2413 | 2414 | *为什么?*:Sinon可以和Jasmine和Mocha合作良好,并且可以扩展它们提供的stubbing和spying。 2415 | 2416 | *为什么?*:如果你想试试Jasmine和Mocha,用Sinon在它们中间来回切换是很方便的。我更喜欢Mocha。 2417 | 2418 | *为什么?*:测试失败Sinon有一个具有描述性的信息。 2419 | 2420 | ### Headless Browser 2421 | ###### [Style [Y194](#style-y194)] 2422 | 2423 | - 在服务器上使用[PhantomJS](http://phantomjs.org/)来运行你的测试。 2424 | 2425 | *为什么?*:PhantomJS是一个headless browser,无需一个“可视”的浏览器来帮助你运行测试。因此你的服务器上不需要安装Chrome、Safari、IE或是其它浏览器。 2426 | 2427 | 注意:你仍然需要在你的环境下测试所有浏览器,来满足用户的需求。 2428 | 2429 | ### 代码分析 2430 | ###### [Style [Y195](#style-y195)] 2431 | 2432 | -在你的测试上运行JSHint。 2433 | 2434 | *为什么?*:测试也是代码,JSHint能够帮你识别代码中可能导致测试无法正常工作的的质量问题。 2435 | 2436 | ### 对测试降低全局JSHint规则 2437 | ###### [Style [Y196](#style-y196) 2438 | 2439 | - 对你的测试代码放宽规则,这样可以允许使用`describe`和`expect`等类似通用的全局方法。对表达式放宽规则,就行Mocha一样。 2440 | 2441 | *为什么?*:测试也是代码,因此要和对待其它生产代码一样重视测试代码的质量。然而,测试框架中允许使用全局变量,例如,在你的测试单例中允许使用this。 2442 | 2443 | ```javascript 2444 | /* jshint -W117, -W030 */ 2445 | ``` 2446 | 或者你也可以把下面的这几行加入到你的JSHint Options文件中。 2447 | 2448 | ```javascript 2449 | "jasmine": true, 2450 | "mocha": true, 2451 | ``` 2452 | 2453 | ![测试工具](https://raw.githubusercontent.com/johnpapa/angular-styleguide/master/a1/assets/testing-tools.png) 2454 | 2455 | ### 组织测试 2456 | ###### [Style [Y197](#style-y197)] 2457 | 2458 | - 将单元测试文件(specs)同被测试客户端代码并列放在同一个文件夹下,将多个组件共用的测试文件以及服务器集成测试的文件放到一个单独的`tests`文件夹下。 2459 | 2460 | *为什么?*:单元测试和源代码中的每一个组件和文件都有直接的相关性。 2461 | 2462 | *为什么?*:这样它就会一直在你的视野中,很容易让它们保持在最新状态。编码的时候无论你做TDD还是在开发过程中测试,或者开发完成后测试,这些单测都不会脱离你的视线和脑海,这样就更容易维护,也有助于保持代码的覆盖率。 2463 | 2464 | *为什么?*:更新源代码的时候可以更简单地在同一时间更新测试代码。 2465 | 2466 | *为什么?*:方便源码阅读者了解组件如何使用,也便于发现其中的局限性。 2467 | 2468 | *为什么?*:方便找。 2469 | 2470 | *为什么?*:方便使用grunt或者gulp。 2471 | 2472 | ``` 2473 | /src/client/app/customers/customer-detail.controller.js 2474 | /customer-detail.controller.spec.js 2475 | /customers.controller.js 2476 | /customers.controller.spec.js 2477 | /customers.module.js 2478 | /customers.route.js 2479 | /customers.route.spec.js 2480 | ``` 2481 | 2482 | **[返回顶部](#目录)** 2483 | 2484 | ## 动画 2485 | 2486 | ### 用法 2487 | ###### [Style [Y210](#style-y210)] 2488 | 2489 | - 在页面过渡时使用[Angular动画](https://docs.angularjs.org/guide/animations),包括[ngAnimate模块](https://docs.angularjs.org/api/ngAnimate)。三个关键点是细微、平滑、无缝。 2490 | 2491 | *为什么?*:使用得当的话能够提高用户体验。 2492 | 2493 | *为什么?*:当视图过渡时,微小的动画可以提高感知性。 2494 | 2495 | ### Sub Second 2496 | ###### [Style [Y211](#style-y211)] 2497 | 2498 | - 使用短持续性的动画,我一般使用300ms,然后调整到合适的时间。 2499 | 2500 | *为什么?*:长时间的动画容易造成用户认为程序性能太差的影响。 2501 | 2502 | ### animate.css 2503 | ###### [Style [Y212](#style-y212)] 2504 | 2505 | - 传统动画使用[animate.css](http://daneden.github.io/animate.css/)。 2506 | 2507 | *为什么?*:css提供的动画是快速的、流畅的、易于添加到应用程序中的。 2508 | 2509 | *为什么?*:为动画提供一致性。 2510 | 2511 | *为什么?*:animate.css被广泛使用和测试。 2512 | 2513 | 注意:参阅[Matias Niemelä的关于Angular动画的文章](http://www.yearofmoo.com/2013/08/remastered-animation-in-angularjs-1-2.html) 2514 | 2515 | **[返回顶部](#目录)** 2516 | 2517 | ## 注释 2518 | 2519 | ### jsDoc 2520 | ###### [Style [Y220](#style-y220)] 2521 | 2522 | - 如果你准备做一个文档,那么就使用[`jsDoc`](http://usejsdoc.org/)的语法来记录函数名、描述、参数和返回值。使用`@namespace`和`@memberOf`来匹配应用程序结构。 2523 | 2524 | *为什么?*:你可以从代码中生成(重新生成)文档,而不必从头开始编写文档。 2525 | 2526 | *为什么?*:使用业内通用工具保持了统一性。 2527 | 2528 | ```javascript 2529 | /** 2530 | * Logger Factory 2531 | * @namespace Factories 2532 | */ 2533 | (function() { 2534 | angular 2535 | .module('app') 2536 | .factory('logger', logger); 2537 | 2538 | /** 2539 | * @namespace Logger 2540 | * @desc Application wide logger 2541 | * @memberOf Factories 2542 | */ 2543 | function logger ($log) { 2544 | var service = { 2545 | logError: logError 2546 | }; 2547 | return service; 2548 | 2549 | //////////// 2550 | 2551 | /** 2552 | * @name logError 2553 | * @desc Logs errors 2554 | * @param {String} msg Message to log 2555 | * @returns {String} 2556 | * @memberOf Factories.Logger 2557 | */ 2558 | function logError(msg) { 2559 | var loggedMsg = 'Error: ' + msg; 2560 | $log.error(loggedMsg); 2561 | return loggedMsg; 2562 | }; 2563 | } 2564 | })(); 2565 | ``` 2566 | 2567 | **[返回顶部](#目录)** 2568 | 2569 | ## JS Hint 2570 | 2571 | ### 使用一个Options文件 2572 | ###### [Style [Y230](#style-y230)] 2573 | 2574 | - 用JS Hint来分析你的JavaScript代码,确保你自定义了JS Hint选项文件并且包含在源控制里。详细信息:[JS Hint文档](http://www.jshint.com/docs/)。 2575 | 2576 | *为什么?*:提交代码到原版本之前先发出警告。 2577 | 2578 | *为什么?*:统一性。 2579 | 2580 | ```javascript 2581 | { 2582 | "bitwise": true, 2583 | "camelcase": true, 2584 | "curly": true, 2585 | "eqeqeq": true, 2586 | "es3": false, 2587 | "forin": true, 2588 | "freeze": true, 2589 | "immed": true, 2590 | "indent": 4, 2591 | "latedef": "nofunc", 2592 | "newcap": true, 2593 | "noarg": true, 2594 | "noempty": true, 2595 | "nonbsp": true, 2596 | "nonew": true, 2597 | "plusplus": false, 2598 | "quotmark": "single", 2599 | "undef": true, 2600 | "unused": false, 2601 | "strict": false, 2602 | "maxparams": 10, 2603 | "maxdepth": 5, 2604 | "maxstatements": 40, 2605 | "maxcomplexity": 8, 2606 | "maxlen": 120, 2607 | 2608 | "asi": false, 2609 | "boss": false, 2610 | "debug": false, 2611 | "eqnull": true, 2612 | "esnext": false, 2613 | "evil": false, 2614 | "expr": false, 2615 | "funcscope": false, 2616 | "globalstrict": false, 2617 | "iterator": false, 2618 | "lastsemic": false, 2619 | "laxbreak": false, 2620 | "laxcomma": false, 2621 | "loopfunc": true, 2622 | "maxerr": false, 2623 | "moz": false, 2624 | "multistr": false, 2625 | "notypeof": false, 2626 | "proto": false, 2627 | "scripturl": false, 2628 | "shadow": false, 2629 | "sub": true, 2630 | "supernew": false, 2631 | "validthis": false, 2632 | "noyield": false, 2633 | 2634 | "browser": true, 2635 | "node": true, 2636 | 2637 | "globals": { 2638 | "angular": false, 2639 | "$": false 2640 | } 2641 | } 2642 | ``` 2643 | 2644 | **[返回顶部](#目录)** 2645 | 2646 | ## JSCS 2647 | 2648 | ### 用一个Options文件 2649 | ###### [Style [Y235](#style-y235)] 2650 | 2651 | - 使用JSCS检查代码规范,确保你的代码控制中有定制的JSCS options文件,在这里[JSCS docs](http://www.jscs.info)查看更多信息。 2652 | 2653 | *为什么?*:提交代码前第一时间提供一个预警。 2654 | 2655 | *为什么?*:保持团队的一致性。 2656 | 2657 | ```javascript 2658 | { 2659 | "excludeFiles": ["node_modules/**", "bower_components/**"], 2660 | 2661 | "requireCurlyBraces": [ 2662 | "if", 2663 | "else", 2664 | "for", 2665 | "while", 2666 | "do", 2667 | "try", 2668 | "catch" 2669 | ], 2670 | "requireOperatorBeforeLineBreak": true, 2671 | "requireCamelCaseOrUpperCaseIdentifiers": true, 2672 | "maximumLineLength": { 2673 | "value": 100, 2674 | "allowComments": true, 2675 | "allowRegex": true 2676 | }, 2677 | "validateIndentation": 4, 2678 | "validateQuoteMarks": "'", 2679 | 2680 | "disallowMultipleLineStrings": true, 2681 | "disallowMixedSpacesAndTabs": true, 2682 | "disallowTrailingWhitespace": true, 2683 | "disallowSpaceAfterPrefixUnaryOperators": true, 2684 | "disallowMultipleVarDecl": null, 2685 | 2686 | "requireSpaceAfterKeywords": [ 2687 | "if", 2688 | "else", 2689 | "for", 2690 | "while", 2691 | "do", 2692 | "switch", 2693 | "return", 2694 | "try", 2695 | "catch" 2696 | ], 2697 | "requireSpaceBeforeBinaryOperators": [ 2698 | "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", 2699 | "&=", "|=", "^=", "+=", 2700 | 2701 | "+", "-", "*", "/", "%", "<<", ">>", ">>>", "&", 2702 | "|", "^", "&&", "||", "===", "==", ">=", 2703 | "<=", "<", ">", "!=", "!==" 2704 | ], 2705 | "requireSpaceAfterBinaryOperators": true, 2706 | "requireSpacesInConditionalExpression": true, 2707 | "requireSpaceBeforeBlockStatements": true, 2708 | "requireLineFeedAtFileEnd": true, 2709 | "disallowSpacesInsideObjectBrackets": "all", 2710 | "disallowSpacesInsideArrayBrackets": "all", 2711 | "disallowSpacesInsideParentheses": true, 2712 | 2713 | "validateJSDoc": { 2714 | "checkParamNames": true, 2715 | "requireParamTypes": true 2716 | }, 2717 | 2718 | "disallowMultipleLineBreaks": true, 2719 | 2720 | "disallowCommaBeforeLineBreak": null, 2721 | "disallowDanglingUnderscores": null, 2722 | "disallowEmptyBlocks": null, 2723 | "disallowMultipleLineStrings": null, 2724 | "disallowTrailingComma": null, 2725 | "requireCommaBeforeLineBreak": null, 2726 | "requireDotNotation": null, 2727 | "requireMultipleVarDecl": null, 2728 | "requireParenthesesAroundIIFE": true 2729 | } 2730 | ``` 2731 | 2732 | **[返回顶部](#目录)** 2733 | 2734 | ## 常量 2735 | 2736 | ### 供应全局变量 2737 | ###### [Style [Y240](#style-y240)] 2738 | 2739 | - 为供应库中的全局变量创建一个Angular常量。 2740 | 2741 | *为什么?*:提供一种注入到供应库的方法,否则就是全局变量。通过让你更容易地了解你的组件之间的依赖关系来提高代码的可测试性。这还允许你模拟这些依赖关系,这是很有意义的。 2742 | 2743 | ```javascript 2744 | // constants.js 2745 | 2746 | /* global toastr:false, moment:false */ 2747 | (function() { 2748 | 'use strict'; 2749 | 2750 | angular 2751 | .module('app.core') 2752 | .constant('toastr', toastr) 2753 | .constant('moment', moment); 2754 | })(); 2755 | ``` 2756 | 2757 | ###### [Style [Y241](#style-y241)] 2758 | 2759 | - 对于一些不需要变动,也不需要从其它service中获取的值,使用常量定义,当一些常量只是在一个模块中使用但是有可能会在其它应用中使用的话,把它们写到一个以当前的模块命名的文件中。把常量集合到一起是非常有必要的,你可以把它们写到`constants.js`的文件中。 2760 | 2761 | *为什么?*:一个可能变化的值,即使变动的很少,也会从service中重新被检索,因此你不需要修改源代码。例如,一个数据服务的url可以被放到一个常量中,但是更好的的做法是把它放到一个web service中。 2762 | 2763 | *为什么?*:常量可以被注入到任何angular组件中,包括providers。 2764 | 2765 | *为什么?*:当一个应用程序被分割成很多可以在其它应用程序中复用的小模块时,每个独立的模块都应该可以操作它自己包含的相关常量。 2766 | 2767 | ```javascript 2768 | // Constants used by the entire app 2769 | angular 2770 | .module('app.core') 2771 | .constant('moment', moment); 2772 | 2773 | // Constants used only by the sales module 2774 | angular 2775 | .module('app.sales') 2776 | .constant('events', { 2777 | ORDER_CREATED: 'event_order_created', 2778 | INVENTORY_DEPLETED: 'event_inventory_depleted' 2779 | }); 2780 | ``` 2781 | 2782 | **[返回顶部](#目录)** 2783 | 2784 | ## 文件模板和片段 2785 | 为了遵循一致的规范和模式,使用文件模板和片段,这里有针对一些web开发的编辑器和IDE的模板和(或)片段。 2786 | 2787 | ### Sublime Text 2788 | ###### [Style [Y250](#style-y250)] 2789 | 2790 | - Angular片段遵循这些规范。 2791 | 2792 | - 下载[Sublime Angular snippets](assets/sublime-angular-snippets?raw=true) 2793 | - 把它放到Packages文件夹中 2794 | - 重启Sublime 2795 | - 在JavaScript文件中输入下面的命令然后按下`TAB`键即可: 2796 | 2797 | ```javascript 2798 | ngcontroller // creates an Angular controller 2799 | ngdirective // creates an Angular directive 2800 | ngfactory // creates an Angular factory 2801 | ngmodule // creates an Angular module 2802 | ngservice // creates an Angular service 2803 | ngfilter // creates an Angular filter 2804 | ``` 2805 | 2806 | ### Visual Studio 2807 | ###### [Style [Y251](#style-y251)] 2808 | 2809 | - Angular文件遵循[SideWaffle](http://www.sidewaffle.com)所介绍的规范。 2810 | 2811 | - 下载Visual Studio扩展文件[SideWaffle](http://www.sidewaffle.com) 2812 | - 运行下载的vsix文件 2813 | - 重启Visual Studio 2814 | 2815 | ### WebStorm 2816 | ###### [Style [Y252](#style-y252)] 2817 | 2818 | - 你可以把它们导入到WebStorm设置中: 2819 | 2820 | - 下载[WebStorm Angular file templates and snippets](https://github.com/johnpapa/angular-styleguide/blob/master/assets/webstorm-angular-file-template.settings.jar?raw=true) 2821 | - 打开WebStorm点击`File`菜单 2822 | - 选择`Import Settings`菜单选项 2823 | - 选择文件点击`OK` 2824 | - 在JavaScript文件中输入下面的命令然后按下`TAB`键即可: 2825 | 2826 | ```javascript 2827 | ng-c // creates an Angular controller 2828 | ng-f // creates an Angular factory 2829 | ng-m // creates an Angular module 2830 | ``` 2831 | 2832 | ### Atom 2833 | ###### [Style [Y253](#style-y253)] 2834 | 2835 | - Angular片段遵循以下规范。 2836 | ``` 2837 | apm install angularjs-styleguide-snippets 2838 | ``` 2839 | 或 2840 | - 打开Atom,打开包管理器(Packages -> Settings View -> Install Packages/Themes) 2841 | - 搜索'angularjs-styleguide-snippets' 2842 | - 点击'Install' 进行安装 2843 | 2844 | - JavaScript文件中输入以下命令后以`TAB`结束 2845 | 2846 | ```javascript 2847 | ngcontroller // creates an Angular controller 2848 | ngdirective // creates an Angular directive 2849 | ngfactory // creates an Angular factory 2850 | ngmodule // creates an Angular module 2851 | ngservice // creates an Angular service 2852 | ngfilter // creates an Angular filter 2853 | ``` 2854 | 2855 | ### Brackets 2856 | ###### [Style [Y254](#style-y254)] 2857 | 2858 | - Angular代码片段遵循以下规范。 2859 | 2860 | - 下载[Brackets Angular snippets](assets/brackets-angular-snippets.yaml?raw=true) 2861 | - 拓展管理器( File > Extension manager ) 2862 | - 安装['Brackets Snippets (by edc)'](https://github.com/chuyik/brackets-snippets) 2863 | - Click the light bulb in brackets' right gutter 2864 | - Click `Settings` and then `Import` 2865 | - Choose the file and select to skip or override 2866 | - Click `Start Import` 2867 | 2868 | - JavaScript文件中输入以下命令后以`TAB`结束 2869 | 2870 | ```javascript 2871 | // These are full file snippets containing an IIFE 2872 | ngcontroller // creates an Angular controller 2873 | ngdirective // creates an Angular directive 2874 | ngfactory // creates an Angular factory 2875 | ngapp // creates an Angular module setter 2876 | ngservice // creates an Angular service 2877 | ngfilter // creates an Angular filter 2878 | 2879 | // These are partial snippets intended to chained 2880 | ngmodule // creates an Angular module getter 2881 | ngstate // creates an Angular UI Router state defintion 2882 | ngconfig // defines a configuration phase function 2883 | ngrun // defines a run phase function 2884 | ngroute // creates an Angular routeProvider 2885 | ``` 2886 | 2887 | ### vim 2888 | ###### [Style [Y255](#style-y255)] 2889 | 2890 | - vim代码片段遵循以下规范。 2891 | 2892 | - 下载[vim Angular代码段](assets/vim-angular-snippets?raw=true) 2893 | - 设置[neosnippet.vim](https://github.com/Shougo/neosnippet.vim) 2894 | - 粘贴到snippet路径下 2895 | 2896 | ```javascript 2897 | ngcontroller // creates an Angular controller 2898 | ngdirective // creates an Angular directive 2899 | ngfactory // creates an Angular factory 2900 | ngmodule // creates an Angular module 2901 | ngservice // creates an Angular service 2902 | ngfilter // creates an Angular filter 2903 | ``` 2904 | **[返回顶部](#目录)** 2905 | 2906 | ## Yeoman Generator 2907 | ###### [Style [Y260](#style-y260)] 2908 | 2909 | 你可以使用[HotTowel yeoman generator](http://jpapa.me/yohottowel)来创建一个遵循本规范的Angular入门应用。 2910 | 2911 | 1. 安装generator-hottowel 2912 | 2913 | ``` 2914 | npm install -g generator-hottowel 2915 | ``` 2916 | 2917 | 2. 创建一个新的文件夹并定位到它 2918 | 2919 | ``` 2920 | mkdir myapp 2921 | cd myapp 2922 | ``` 2923 | 2924 | 3. 运行生成器 2925 | 2926 | ``` 2927 | yo hottowel helloWorld 2928 | ``` 2929 | 2930 | **[返回顶部](#目录)** 2931 | 2932 | ## 路由 2933 | 客户端路由对于在视图和很多小模板和指令组成的构成视图中创建导航是非常重要的。 2934 | 2935 | ###### [Style [Y270](#style-y270)] 2936 | 2937 | - 用[AngularUI Router](http://angular-ui.github.io/ui-router/)来做路由控制。 2938 | 2939 | *为什么?*:它包含了Angular路由的所有特性,并且增加了一些额外的特性,如嵌套路由和状态。 2940 | 2941 | *为什么?*:语法和Angular路由很像,很容易迁移到UI Router。 2942 | 2943 | - 注意:你可以在运行期间使用`routerHelperProvider`配置跨文件状态 2944 | 2945 | ```javascript 2946 | // customers.routes.js 2947 | angular 2948 | .module('app.customers') 2949 | .run(appRun); 2950 | 2951 | /* @ngInject */ 2952 | function appRun(routerHelper) { 2953 | routerHelper.configureStates(getStates()); 2954 | } 2955 | 2956 | function getStates() { 2957 | return [ 2958 | { 2959 | state: 'customer', 2960 | config: { 2961 | abstract: true, 2962 | template: '', 2963 | url: '/customer' 2964 | } 2965 | } 2966 | ]; 2967 | } 2968 | ``` 2969 | 2970 | ```javascript 2971 | // routerHelperProvider.js 2972 | angular 2973 | .module('blocks.router') 2974 | .provider('routerHelper', routerHelperProvider); 2975 | 2976 | routerHelperProvider.$inject = ['$locationProvider', '$stateProvider', '$urlRouterProvider']; 2977 | /* @ngInject */ 2978 | function routerHelperProvider($locationProvider, $stateProvider, $urlRouterProvider) { 2979 | /* jshint validthis:true */ 2980 | this.$get = RouterHelper; 2981 | 2982 | $locationProvider.html5Mode(true); 2983 | 2984 | RouterHelper.$inject = ['$state']; 2985 | /* @ngInject */ 2986 | function RouterHelper($state) { 2987 | var hasOtherwise = false; 2988 | 2989 | var service = { 2990 | configureStates: configureStates, 2991 | getStates: getStates 2992 | }; 2993 | 2994 | return service; 2995 | 2996 | /////////////// 2997 | 2998 | function configureStates(states, otherwisePath) { 2999 | states.forEach(function(state) { 3000 | $stateProvider.state(state.state, state.config); 3001 | }); 3002 | if (otherwisePath && !hasOtherwise) { 3003 | hasOtherwise = true; 3004 | $urlRouterProvider.otherwise(otherwisePath); 3005 | } 3006 | } 3007 | 3008 | function getStates() { return $state.get(); } 3009 | } 3010 | } 3011 | ``` 3012 | 3013 | ###### [Style [Y271](#style-y271)] 3014 | 3015 | - Define routes for views in the module where they exist,Each module should contain the routes for the views in the module. 3016 | 3017 | *为什么?*:每个模块应该是独立的。 3018 | 3019 | *为什么?*:当删除或增加一个模块时,应用程序只包含指向现存视图的路由。(也就是说删除模块和增加模块都需更新路由) 3020 | 3021 | *为什么?*:这使得可以在不关心孤立的路由时很方便地启用或禁用应用程序的某些部分。 3022 | 3023 | **[返回顶部](#目录)** 3024 | 3025 | ## 任务自动化 3026 | 用[Gulp](http://gulpjs.com)或者[Grunt](http://gruntjs.com)来创建自动化任务。Gulp偏向于代码优先原则(code over configuration)而Grunt更倾向于配置优先原则(configuration over code)。我更倾向于使用gulp,因为gulp写起来比较简单。 3027 | 3028 | > 可以在我的[Gulp Pluralsight course](http://jpapa.me/gulpps)了解更多gulp和自动化任务的信息 3029 | 3030 | ###### [Style [Y400](#style-y400)] 3031 | 3032 | - 用任务自动化在其它JavaScript文件之前列出所有模块的定义文件`*.module.js`。 3033 | 3034 | *为什么?*:Angular中,模块使用之前必须先注册。 3035 | 3036 | *为什么?*:带有特殊规则的模块命名,例如`*.module.js`,会让你很轻松地识别它们。 3037 | 3038 | ```javascript 3039 | var clientApp = './src/client/app/'; 3040 | 3041 | // Always grab module files first 3042 | var files = [ 3043 | clientApp + '**/*.module.js', 3044 | clientApp + '**/*.js' 3045 | ]; 3046 | ``` 3047 | 3048 | **[返回顶部](#目录)** 3049 | 3050 | ## Filters 3051 | 3052 | ###### [Style [Y420](#style-y420)] 3053 | 3054 | - 避免使用filters扫描一个复杂对象的所有属性,应该用filters来筛选选择的属性。 3055 | 3056 | *为什么?*:不恰当的使用会造成滥用并且会带来糟糕的性能问题,例如对一个复杂的对象使用过滤器。 3057 | 3058 | **[返回顶部](#目录)** 3059 | 3060 | ## Angular文档 3061 | [Angular文档](//docs.angularjs.org/api)。 3062 | 3063 | ## 贡献 3064 | 3065 | 先打开一个问题讨论潜在的变化和增加。如果你对这篇规范有任何疑惑,随时在仓库中提出问题。如果你发现了一个错字,创建一个pull request。这样做是为了保持内容的更新,使用github的原生功能通过问题和PR来帮助讲述这个故事,具体做法可以google一下。为什么?因为如果你有问题,其他人可能有同样的问题,你在这里可以学到如何贡献。 3066 | 3067 | *贡献代码到这个仓库就意味着你同意了本仓库的许可证内容* 3068 | 3069 | ### 过程 3070 | 1. 在Github Issue中讨论这个问题。 3071 | 2. 拉取一个pull request,引用这个问题,解释你做的修改和为什么要这样做。 3072 | 3. pull request将会被进行评估,结果就是合并或是拒绝。 3073 | 3074 | ## 许可 3075 | 3076 | - **tldr;** 如果可以的话,使用本规范的时候还是指明归属吧。 3077 | 3078 | ### Copyright 3079 | 3080 | Copyright (c) 2014-2015 [John Papa](http://johnpapa.net) 3081 | 3082 | ### (The MIT License) 3083 | Permission is hereby granted, free of charge, to any person obtaining 3084 | a copy of this software and associated documentation files (the 3085 | 'Software'), to deal in the Software without restriction, including 3086 | without limitation the rights to use, copy, modify, merge, publish, 3087 | distribute, sublicense, and/or sell copies of the Software, and to 3088 | permit persons to whom the Software is furnished to do so, subject to 3089 | the following conditions: 3090 | 3091 | The above copyright notice and this permission notice shall be 3092 | included in all copies or substantial portions of the Software. 3093 | 3094 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 3095 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 3096 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 3097 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 3098 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 3099 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 3100 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 3101 | 3102 | **[返回顶部](#目录)** 3103 | -------------------------------------------------------------------------------- /a2/README.md: -------------------------------------------------------------------------------- 1 | # Angular 2 Style Guide 2 | Style Guides require experience building applications with the tools. My style guide for Angular 1 was based on years of experience with Angular 1, collaboration with other Angular experts, and contributions from the Angular core team. Nobody has the equivalent massive experience with Angular 2, and thus the Angular 2 Style Guide is a work in progress. 3 | 4 | My intent is to release the guide as a living document. Guidelines that we are comfortable recommending will be included. Be wary and definitely ask questions when someone, including me, publishes a guide :) 5 | 6 | ## Angular Team Endorsed 7 | Special thanks to Igor Minar, lead on the Angular team, for reviewing, contributing feedback, and entrusting me to shepherd this guide. 8 | 9 | ## Purpose 10 | *Opinionated Angular style guide for teams by [@john_papa](//twitter.com/john_papa)* 11 | 12 | If you are looking for an opinionated style guide for syntax, conventions, and structuring Angular applications, then step right in. These styles are based on development experience with [Angular](//angularjs.org), presentations, [Pluralsight training courses](http://app.pluralsight.com/author/john-papa) and working in teams. 13 | 14 | The purpose of this style guide is to provide guidance on building Angular applications by showing the conventions I use and, more importantly, why I choose them. 15 | 16 | ## Relocating the Angular 2 Style Guide 17 | The Angular 2 Style Guide has been moved to the [Official Angular 2 docs](http://jpapa.me/ng2styleguide). I will still be shepherding the guide there. It is a living guide ... we'll keep evolving the guide. 18 | 19 | ## Appendix 20 | 21 | >If you like this guide, check out my [Angular 2 First Look course on Pluralsight](http://jpapa.me/a2ps1stlook). 22 | 23 | ![Angular 2 First Look](https://s3-us-west-2.amazonaws.com/johnpapa-blog-images/a2-first-look-app.gif) 24 | 25 | **[Back to top](#table-of-contents)** 26 | -------------------------------------------------------------------------------- /a2/notes.md: -------------------------------------------------------------------------------- 1 | # Angular 2 Style Guide 2 | Style Guides require experience building applications with the tools. My style guide for Angular 1 was based on years of experience with Angular 1, collaboration with other Angular experts, and contributions from the Angular core team. Nobody has the same massive experience with Angular 2, and thus the Angular 2 Style Guide is a work in progress. 3 | 4 | My intent is to release the guide as a living document. Guidelines that we are comfortable recommending will be included. By wary and definitely ask questions when someone, including me, publishes a guide :) 5 | 6 | ## Angular Team Endorsed 7 | Special thanks to Igor Minar, lead on the Angular team, for reviewing, contributing feedback, and entrusting me to shepherd this guide. 8 | 9 | ## Purpose 10 | *Opinionated Angular style guide for teams by [@john_papa](//twitter.com/john_papa)* 11 | 12 | If you are looking for an opinionated style guide for syntax, conventions, and structuring Angular applications, then step right in. These styles are based on my development experience with [Angular](//angularjs.org), presentations, [Pluralsight training courses](http://app.pluralsight.com/author/john-papa) and working in teams. 13 | 14 | The purpose of this style guide is to provide guidance on building Angular applications by showing the conventions I use and, more importantly, why I choose them. 15 | 16 | >If you like this guide, check out my Angular 2 First Look course **coming soon** to Pluralsight. 17 | 18 | ## Community Awesomeness and Credit 19 | Never work in a vacuum. I find that the Angular community is an incredible group who are passionate about sharing experiences. Many of my styles have been from the many pair programming sessions [Ward Bell](https://twitter.com/wardbell) and I have had. My friend Ward has certainly helped influence the ultimate evolution of this guide. 20 | 21 | ##Translations 22 | Translations of this Angular 2 style guide are maintained by the community. Due to the in flux nature of this guide, I will hold off on accepting contributions for the time being. But I hope to get the same amazing contributions we had for the v1 of the guide! 23 | 24 | ## Table of Contents 25 | 26 | 1. [Single Responsibility](#single-responsibility) 27 | 1. [IIFE](#iife) 28 | 1. [Modules](#modules) 29 | 1. [Components](#components) 30 | 1. [Services](#services) 31 | 1. [Factories](#factories) 32 | 1. [Data Services](#data-services) 33 | 1. [Directives](#directives) 34 | 1. [Resolving Promises](#resolving-promises) 35 | 1. [Manual Annotating for Dependency Injection](#manual-annotating-for-dependency-injection) 36 | 1. [Minification and Annotation](#minification-and-annotation) 37 | 1. [Exception Handling](#exception-handling) 38 | 1. [Naming](#naming) 39 | 1. [Application Structure LIFT Principle](#application-structure-lift-principle) 40 | 1. [Application Structure](#application-structure) 41 | 1. [Modularity](#modularity) 42 | 1. [Startup Logic](#startup-logic) 43 | 1. [Angular $ Wrapper Services](#angular--wrapper-services) 44 | 1. [Testing](#testing) 45 | 1. [Animations](#animations) 46 | 1. [Comments](#comments) 47 | 1. [JSHint](#js-hint) 48 | 1. [JSCS](#jscs) 49 | 1. [Constants](#constants) 50 | 1. [File Templates and Snippets](#file-templates-and-snippets) 51 | 1. [Yeoman Generator](#yeoman-generator) 52 | 1. [Routing](#routing) 53 | 1. [Task Automation](#task-automation) 54 | 1. [Filters](#filters) 55 | 1. [Angular Docs](#angular-docs) 56 | 1. [Contributing](#contributing) 57 | 1. [License](#license) 58 | 59 | ## Single Responsibility 60 | 61 | ### Rule of 1 62 | ###### [Style [A2-001](#style-a2-001)] 63 | 64 | - Define 1 component per file, recommended to be less than 500 lines of code. 65 | 66 | *Why?*: One component per file promotes easier unit testing and mocking. 67 | 68 | *Why?*: One component per file makes it far easier to read, maintain, and avoid collisions with teams in source control. 69 | 70 | *Why?*: One component per file avoids hidden bugs that often arise when combining components in a file where they may share variables, create unwanted closures, or unwanted coupling with dependencies. 71 | 72 | The following example defines the `app` module and its dependencies, defines a component, and defines a factory all in the same file. 73 | 74 | **example coming soon** 75 | 76 | **[Back to top](#table-of-contents)** 77 | 78 | ### Small Functions 79 | ###### [Style [A2-002](#style-a2-002)] 80 | 81 | - Define small functions, no more than 75 LOC (less is better). 82 | 83 | *Why?*: Small functions are easier to test, especially when they do one thing and serve one purpose. 84 | 85 | *Why?*: Small functions promote reuse. 86 | 87 | *Why?*: Small functions are easier to read. 88 | 89 | *Why?*: Small functions are easier to maintain. 90 | 91 | *Why?*: Small functions help avoid hidden bugs that come with large functions that share variables with external scope, create unwanted closures, or unwanted coupling with dependencies. 92 | 93 | **[Back to top](#table-of-contents)** 94 | 95 | ## Modules 96 | 97 | ### Avoid Naming Collisions 98 | ###### [Style [A2-020](#style-a2-020)] 99 | 100 | - Use unique naming conventions with separators for sub-modules. 101 | 102 | *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`. 103 | 104 | ### Definitions (aka Setters) 105 | ###### [Style [A2-021](#style-a2-021)] 106 | 107 | ## Components 108 | 109 | ### Bindable Members Up Top 110 | ###### [Style [A2-033](#style-a2-033)] 111 | 112 | - Place bindable members at the top of the component, alphabetized, and not spread through the component code. 113 | 114 | *Why?*: Placing bindable members at the top makes it easy to read and helps you instantly identify which members of the component can be bound and used in the View. 115 | 116 | *Why?*: Setting anonymous functions in-line can be easy, but when those functions are more than 1 line of code they can reduce the readability. Defining the functions below the bindable members (the functions will be hoisted) moves the implementation details down, keeps the bindable members up top, and makes it easier to read. 117 | 118 | **example coming soon** 119 | 120 | ### Defer Logic to Services 121 | ###### [Style [A2-035](#style-a2-035)] 122 | 123 | - Defer logic in a component by delegating to services. 124 | 125 | *Why?*: Logic may be reused by multiple components when placed within a service and exposed via a function. 126 | 127 | *Why?*: Logic in a service can more easily be isolated in a unit test, while the calling logic in the component can be easily mocked. 128 | 129 | *Why?*: Removes dependencies and hides implementation details from the component. 130 | 131 | *Why?*: Keeps the component slim, trim, and focused. 132 | 133 | **example coming soon** 134 | 135 | ### Keep Components Focused 136 | ###### [Style [A2-037](#style-a2-037)] 137 | 138 | - Define a component for a view, and try not to reuse the component for other views. Instead, move reusable logic to factories and keep the component simple and focused on its view. 139 | 140 | *Why?*: Reusing components with several views is brittle and good end-to-end (e2e) test coverage is required to ensure stability across large applications. 141 | 142 | **example coming soon** 143 | 144 | **[Back to top](#table-of-contents)** 145 | 146 | ## Services 147 | 148 | ### Singletons 149 | ###### [Style [A2-040](#style-a2-040)] 150 | 151 | - Services are singletons and should be used for sharing data and functionality. 152 | 153 | **example coming soon** 154 | 155 | **[Back to top](#table-of-contents)** 156 | 157 | ### Single Responsibility 158 | ###### [Style [A2-050](#style-a2-050)] 159 | 160 | - Services should have a [single responsibility](https://en.wikipedia.org/wiki/Single_responsibility_principle), that is encapsulated by its context. Once a service begins to exceed that singular purpose, a new one should be created. 161 | 162 | **example coming soon** 163 | 164 | **[Back to top](#table-of-contents)** 165 | 166 | ### Singletons 167 | ###### [Style [A2-051](#style-a2-051)] 168 | 169 | - Factories are singletons and return an object that contains the members of the service. 170 | 171 | **example coming soon** 172 | 173 | **[Back to top](#table-of-contents)** 174 | 175 | ### Accessible Members Up Top 176 | ###### [Style [A2-052](#style-a2-052)] 177 | 178 | - Expose the callable members of the service (its interface) at the top 179 | 180 | *Why?*: Placing the callable members at the top makes it easy to read and helps you instantly identify which members of the service can be called and must be unit tested (and/or mocked). 181 | 182 | *Why?*: This is especially helpful when the file gets longer as it helps avoid the need to scroll to see what is exposed. 183 | 184 | *Why?*: Setting functions as you go can be easy, but when those functions are more than 1 line of code they can reduce the readability and cause more scrolling. Defining the callable interface via the returned service moves the implementation details down, keeps the callable interface up top, and makes it easier to read. 185 | 186 | **example coming soon** 187 | 188 | **[Back to top](#table-of-contents)** 189 | 190 | ## Data Services 191 | 192 | ### Separate Data Calls 193 | ###### [Style [A2-060](#style-a2-060)] 194 | 195 | - Refactor logic for making data operations and interacting with data to a service. Make data services responsible for XHR calls, local storage, stashing in memory, or any other data operations. 196 | 197 | *Why?*: The component'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 component be simpler and more focused on the view. 198 | 199 | *Why?*: This makes it easier to test (mock or real) the data calls when testing a component that uses a data service. 200 | 201 | *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 component), also making it easier to change the implementation. 202 | 203 | **example coming soon** 204 | 205 | **[Back to top](#table-of-contents)** 206 | 207 | ### Return a Promise or Observable from Data Calls 208 | ###### [Style [A2-061](#style-a2-061)] 209 | 210 | - When calling a data service that returns a promise such as `http`, return a promise or Observable in your calling function too. 211 | 212 | *Why?*: You can chain the promises together and take further action after the data call completes and resolves or rejects the promise. 213 | 214 | **example coming soon** 215 | 216 | **[Back to top](#table-of-contents)** 217 | 218 | ## Directives 219 | ### Limit 1 Per File 220 | ###### [Style [A2-070](#style-a2-070)] 221 | 222 | - Create one directive per file. Name the file for the directive. 223 | 224 | *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. 225 | 226 | *Why?*: One directive per file is easy to maintain. 227 | 228 | ### Manipulate DOM in a Structural Directive 229 | ###### [Style [A2-072](#style-a2-072)] 230 | 231 | - 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. 232 | 233 | *Why?*: DOM manipulation can be difficult to test, debug, and there are often better ways (e.g. CSS, animations, templates) 234 | 235 | ### Provide a Unique Directive Prefix 236 | ###### [Style [A2-073](#style-a2-073)] 237 | 238 | - Provide a short, unique and descriptive directive prefix such as `acmeSalesCustomerInfo` which would be declared in HTML as `acme-sales-customer-info`. 239 | 240 | *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. 241 | 242 | 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/). 243 | 244 | **example coming soon** 245 | 246 | **[Back to top](#table-of-contents)** 247 | 248 | ## Resolving Promises 249 | ### Component Activation Promises 250 | ###### [Style [A2-080](#style-a2-080)] 251 | 252 | - Resolve start-up logic for a component in an `activate` function. 253 | 254 | *Why?*: Placing start-up logic in a consistent place in the component makes it easier to locate, more consistent to test, and helps avoid spreading out the activation logic across the component. 255 | 256 | *Why?*: The component `activate` makes it convenient to re-use the logic for a refresh for the component/View, keeps the logic together, gets the user to the View faster, makes animations easy on the `ng-view` or `ui-view`, and feels snappier to the user. 257 | 258 | Note: If you need to conditionally cancel the route before you start using the component, use a [route resolve](A2-style-a2-081) instead. 259 | 260 | **example coming soon** 261 | 262 | **[Back to top](#table-of-contents)** 263 | 264 | ### Route Resolve Promises 265 | ###### [Style [A2-081](#style-a2-081)] 266 | 267 | - When a component depends on a promise to be resolved before the component is activated, resolve those dependencies in the `$routeProvider` before the component logic is executed. If you need to conditionally cancel a route before the component is activated, use a route resolver. 268 | 269 | - Use a route resolve when you want to decide to cancel the route before ever transitioning to the View. 270 | 271 | *Why?*: A component 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 component logic executes, so it might take action based on that data from the promise. 272 | 273 | *Why?*: The code executes after the route and in the component’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`) 274 | 275 | 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 [component `activate` technique](#style-aA2--080) instead. 276 | 277 | **example coming soon** 278 | 279 | **[Back to top](#table-of-contents)** 280 | 281 | ### Handling Exceptions with Promises 282 | ###### [Style [A2-082](#style-a2-082)] 283 | 284 | - The `catch` block of a promise must return a rejected promise to maintain the exception in the promise chain. 285 | 286 | - Always handle exceptions in services/factories. 287 | 288 | *Why?*: If the `catch` block does not return a rejected promise, the caller of the promise will not know an exception occurred. The caller's `then` will execute. Thus, the user may never know what happened. 289 | 290 | *Why?*: To avoid swallowing errors and misinforming the user. 291 | 292 | Note: Consider putting any exception handling in a function in a shared module and service. 293 | 294 | **example coming soon** 295 | 296 | **[Back to top](#table-of-contents)** 297 | 298 | ## Exception Handling 299 | 300 | ### Exception Catchers 301 | ###### [Style [A2-111](#style-a2-111)] 302 | 303 | - Create a service that exposes an interface to catch and gracefully handle exceptions. 304 | 305 | *Why?*: Provides a consistent way to catch exceptions that may be thrown in your code (e.g. during XHR calls or promise failures). 306 | 307 | 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. 308 | 309 | **example coming soon** 310 | 311 | **[Back to top](#table-of-contents)** 312 | 313 | ## Naming 314 | 315 | ### Naming Guidelines 316 | ###### [Style [A2-120](#style-a2-120)] 317 | 318 | - Use consistent names for all components following a pattern that describes the component's feature then (optionally) its type. My recommended pattern is `feature.type.js`. There are 2 names for most assets: 319 | * the file name (`avengers.component.ts`) 320 | * the registered component name with Angular (`Component`) 321 | 322 | *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. 323 | 324 | *Why?*: The naming conventions should simply help you find your code faster and make it easier to understand. 325 | 326 | **example coming soon** 327 | 328 | **[Back to top](#table-of-contents)** 329 | 330 | ### Feature File Names 331 | ###### [Style [A2-121](#style-a2-121)] 332 | 333 | - Use consistent names for all components following a pattern that describes the component's feature then (optionally) its type. My recommended pattern is `feature.type.ts`. 334 | 335 | *Why?*: Provides a consistent way to quickly identify components. 336 | 337 | *Why?*: Provides pattern matching for any automated tasks. 338 | 339 | ### Test File Names 340 | ###### [Style [A2-122](#style-a2-122)] 341 | 342 | - Name test specifications similar to the component they test with a suffix of `spec`. 343 | 344 | *Why?*: Provides a consistent way to quickly identify components. 345 | 346 | *Why?*: Provides pattern matching for [karma](http://karma-runner.github.io/) or other test runners. 347 | 348 | **example coming soon** 349 | 350 | **[Back to top](#table-of-contents)** 351 | 352 | ### Component Names 353 | ###### [Style [A2-123](#style-a2-123)] 354 | 355 | - Use consistent names for all components named after their feature. Use UpperCamelCase for components, as they are constructors. 356 | 357 | *Why?*: Provides a consistent way to quickly identify and reference components. 358 | 359 | *Why?*: UpperCamelCase is conventional for identifying object that can be instantiated using a constructor. 360 | 361 | **example coming soon** 362 | 363 | **[Back to top](#table-of-contents)** 364 | 365 | ### Component Name Suffix 366 | ###### [Style [A2-124](#style-a2-124)] 367 | 368 | - Append the component name with the suffix `Component`. 369 | 370 | *Why?*: The `Component` suffix is more commonly used and is more explicitly descriptive. 371 | 372 | ```typescript 373 | /** 374 | * recommended 375 | */ 376 | 377 | // avengers.component.ts 378 | export class AvengersComponent { } 379 | ``` 380 | 381 | ### Service Names 382 | ###### [Style [A2-125](#style-a2-125)] 383 | 384 | - Use consistent names for all services named after their feature. Use UpperCamelCase for services. Only suffix service and factories with `Service` when it is not clear what they are (i.e. when they are nouns). 385 | 386 | *Why?*: Provides a consistent way to quickly identify and reference services. 387 | 388 | *Why?*: Clear service names such as `logger` do not require a suffix. 389 | 390 | *Why?*: Service names such as `avengers` are nouns and require a suffix and should be named `AvengersService`. 391 | 392 | **example coming soon** 393 | 394 | **[Back to top](#table-of-contents)** 395 | 396 | ### Directive Component Names 397 | ###### [Style [A2-126](#style-a2-126)] 398 | 399 | - 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). 400 | 401 | *Why?*: Provides a consistent way to quickly identify and reference components. 402 | 403 | **example coming soon** 404 | 405 | **[Back to top](#table-of-contents)** 406 | 407 | ### Routes 408 | ###### [Style [A2-129](#style-a2-129)] 409 | 410 | - Separate route configuration into a routing component file. 411 | 412 | **example coming soon** 413 | 414 | **[Back to top](#table-of-contents)** 415 | 416 | ## Application Structure LIFT Principle 417 | ### LIFT 418 | ###### [Style [Y140](#style-y140)] 419 | 420 | - 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. 421 | 422 | *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? 423 | 424 | When I find my structure is not feeling comfortable, I go back and revisit these LIFT guidelines 425 | 426 | 1. `L`ocating our code is easy 427 | 2. `I`dentify code at a glance 428 | 3. `F`lat structure as long as we can 429 | 4. `T`ry to stay DRY (Don’t Repeat Yourself) or T-DRY 430 | 431 | ### Locate 432 | ###### [Style [Y141](#style-y141)] 433 | 434 | - Make locating your code intuitive, simple and fast. 435 | 436 | *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. 437 | 438 | ``` 439 | /bower_components 440 | /client 441 | /app 442 | /avengers 443 | /blocks 444 | /exception 445 | /logger 446 | /core 447 | /dashboard 448 | /data 449 | /layout 450 | /widgets 451 | /content 452 | index.html 453 | .bower.json 454 | ``` 455 | 456 | ### Identify 457 | ###### [Style [Y142](#style-y142)] 458 | 459 | - When you look at a file you should instantly know what it contains and represents. 460 | 461 | *Why?*: You spend less time hunting and pecking for code, and become more efficient. If this means you want longer file names, then so be it. Be descriptive with file names and keeping the contents of the file to exactly 1 component. Avoid files with multiple controllers, multiple services, or a mixture. There are deviations of the 1 per file rule when I have a set of very small features that are all related to each other, they are still easily identifiable. 462 | 463 | ### Flat 464 | ###### [Style [Y143](#style-y143)] 465 | 466 | - Keep a flat folder structure as long as possible. When you get to 7+ files, begin considering separation. 467 | 468 | *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. 469 | 470 | ### T-DRY (Try to Stick to DRY) 471 | ###### [Style [Y144](#style-y144)] 472 | 473 | - Be DRY, but don't go nuts and sacrifice readability. 474 | 475 | *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. 476 | 477 | **[Back to top](#table-of-contents)** 478 | 479 | ## Application Structure 480 | 481 | ### Overall Guidelines 482 | ###### [Style [A2-150](#style-a2-150)] 483 | 484 | - 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 component, service, pipe 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. 485 | 486 | **example coming soon** 487 | 488 | **[Back to top](#table-of-contents)** 489 | 490 | ### Layout 491 | ###### [Style [A2-151](#style-a2-151)] 492 | 493 | - Place components that define the overall layout of the application in a folder named `layout`. These may include a shell view and component may act as the container for the app, navigation, menus, content areas, and other regions. 494 | 495 | *Why?*: Organizes all layout in a single place re-used throughout the application. 496 | 497 | ### Folders-by-Feature Structure 498 | ###### [Style [A2-152](#style-a2-152)] 499 | 500 | - 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. 501 | 502 | *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. 503 | 504 | *Why?*: The LIFT guidelines are all covered. 505 | 506 | *Why?*: Helps reduce the app from becoming cluttered through organizing the content and keeping them aligned with the LIFT guidelines. 507 | 508 | *Why?*: When there are a lot of files (10+) locating them is easier with a consistent folder structures and more difficult in flat structures. 509 | 510 | **example coming soon** 511 | 512 | **[Back to top](#table-of-contents)** 513 | 514 | ## Modularity 515 | 516 | ### Many Small, Self Contained Modules 517 | ###### [Style [A2-160](#style-a2-160)] 518 | 519 | - Create small modules that encapsulate one responsibility. 520 | 521 | *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. 522 | 523 | **example coming soon** 524 | 525 | **[Back to top](#table-of-contents)** 526 | 527 | ## Startup Logic 528 | 529 | ### Bootstrapping 530 | ###### [Style [A2-170](#style-a2-170)] 531 | 532 | **example coming soon** 533 | 534 | **[Back to top](#table-of-contents)** 535 | 536 | ## Testing 537 | Unit testing helps maintain clean code, as such I included some of my recommendations for unit testing foundations with links for more information. 538 | 539 | ### Write Tests with Stories 540 | ###### [Style [A2-190](#style-a2-190)] 541 | 542 | - 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. 543 | 544 | *Why?*: Writing the test descriptions helps clearly define what your story will do, will not do, and how you can measure success. 545 | 546 | ```javascript 547 | it('should have Avengers component', function() { 548 | // TODO 549 | }); 550 | 551 | it('should find 1 Avenger when filtered by name', function() { 552 | // TODO 553 | }); 554 | 555 | it('should have 10 Avengers', function() { 556 | // TODO (mock data?) 557 | }); 558 | 559 | it('should return Avengers via XHR', function() { 560 | // TODO ($httpBackend?) 561 | }); 562 | 563 | // and so on 564 | ``` 565 | 566 | ### Testing Library 567 | ###### [Style [A2-191](#style-a2-191)] 568 | 569 | - Use [Jasmine](http://jasmine.github.io/) or [Mocha](http://mochajs.org) for unit testing. 570 | 571 | *Why?*: Both Jasmine and Mocha are widely used in the Angular community. Both are stable, well maintained, and provide robust testing features. 572 | 573 | ### Test Runner 574 | ###### [Style [A2-192](#style-a2-192)] 575 | 576 | **example coming soon** 577 | 578 | **[Back to top](#table-of-contents)** 579 | 580 | ### Organizing Tests 581 | ###### [Style [A2-197](#style-a2-197)] 582 | 583 | - 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. 584 | 585 | *Why?*: Unit tests have a direct correlation to a specific component and file in source code. 586 | 587 | *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. 588 | 589 | *Why?*: When you update source code it is easier to go update the tests at the same time. 590 | 591 | *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. 592 | 593 | *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. 594 | 595 | *Why?*: Separating specs so they are not in a distributed build is easy with grunt or gulp. 596 | 597 | **example coming soon** 598 | 599 | **[Back to top](#table-of-contents)** 600 | 601 | ## File Templates and Snippets 602 | Use file templates or snippets to help follow consistent styles and patterns. Here are templates and/or snippets for some of the web development editors and IDEs. 603 | 604 | ### Visual Studio Code 605 | 606 | - [Visual Studio Code](https://code.visualstudio.com/) snippets that follow these styles and guidelines. 607 | 608 | **[Back to top](#table-of-contents)** 609 | 610 | ## Angular CLI 611 | 612 | **[Back to top](#table-of-contents)** 613 | 614 | ## Routing 615 | Client-side routing is important for creating a navigation flow between views and composing views that are made of many smaller templates and directives. 616 | 617 | **[Back to top](#table-of-contents)** 618 | 619 | ## Task Automation 620 | 621 | **[Back to top](#table-of-contents)** 622 | 623 | ## Pipes 624 | 625 | ###### [Style [A2-420](#style-a2-420)] 626 | 627 | **[Back to top](#table-of-contents)** 628 | 629 | ## Angular docs 630 | For anything else, API reference, check the [Angular 2 documentation](//angular.io). 631 | 632 | **[Back to top](#table-of-contents)** 633 | --------------------------------------------------------------------------------