介绍
该样式指南的目标是为一个AngularJS应用程序提供一组最佳实践和样式指南。这些最佳实践是从:
- AngularJS源代码
- 我读过的源代码或文章
- 我自己的经验
注1 :这仍然是样式指南的草稿,其主要目标是以社区为导向,因此填补空白将受到整个社区的极大赞赏。
注2 :在遵循英语文档翻译中的任何指南之前,请确保它们是最新的。 angularjs style guide的最新版本是当前文档。
在此样式指南中,您找不到有关JavaScript开发的常见准则。可以找到:
- Google的JavaScript样式指南
- Mozilla的JavaScript样式指南
- 道格拉斯·克罗克福德(Douglas Crockford)的JavaScript风格指南
- Airbnb JavaScript样式指南
- 惯用JavaScript样式指南
对于AngularJS开发,推荐的是Google的JavaScript样式指南。
在Angularjs的Github Wiki中,Proser有类似的部分,您可以在此处进行检查。
翻译
- 德语
- 西班牙语
- 法语
- 印度尼西亚
- 意大利人
- 日本人
- 韩国人
- 抛光
- 葡萄牙语
- 俄语
- 塞尔维亚
- 塞尔维亚纬度
- 中国人
- 土耳其
内容表
- 一般的
- 目录结构
- 标记
- 命名约定
- 其他的
- 模块
- 控制器
- 指令
- 过滤器
- 服务
- 模板
- 路由
- E2E测试
- i18n
- 表现
- 贡献
- 贡献者
一般的
目录结构
由于大型AngularJS应用程序具有许多组件,因此最好在目录层次结构中构造它。有两种主要方法:
- 通过组件类型和较低级别的划分来创建高级划分。
这样,目录结构看起来像:
.
├── app
│ ├── app.js
│ ├── controllers
│ │ ├── home
│ │ │ ├── FirstCtrl.js
│ │ │ └── FirstCtrl.spec.js
│ │ │ └── SecondCtrl.js
│ │ │ └── SecondCtrl.spec.js
│ │ └── about
│ │ └── ThirdCtrl.js
│ │ └── ThirdCtrl.spec.js
│ ├── directives
│ │ ├── home
│ │ │ └── directive1.js
│ │ │ └── directive1.spec.js
│ │ └── about
│ │ ├── directive2.js
│ │ ├── directive2.spec.js
│ │ └── directive3.js
│ │ └── directive3.spec.js
│ ├── filters
│ │ ├── home
│ │ └── about
│ └── services
│ ├── CommonService.js
│ ├── CommonService.spec.js
│ ├── cache
│ │ ├── Cache1.js
│ │ ├── Cache1.spec.js
│ │ └── Cache2.js
│ │ └── Cache2.spec.js
│ └── models
│ ├── Model1.spec.js
│ ├── Model1.js
│ └── Model2.spec.js
│ └── Model2.js
├── partials
├── lib
└── e2e-tests
- 按功能和下层分区创建高级划分。
这是它的布局:
.
├── app
│ ├── app.js
│ ├── common
│ │ ├── controllers
│ │ ├── directives
│ │ ├── filters
│ │ └── services
│ ├── home
│ │ ├── controllers
│ │ │ ├── FirstCtrl.js
│ │ │ ├── FirstCtrl.spec.js
│ │ │ └── SecondCtrl.js
│ │ │ └── SecondCtrl.spec.js
│ │ ├── directives
│ │ │ └── directive1.js
│ │ │ └── directive1.spec.js
│ │ ├── filters
│ │ │ ├── filter1.js
│ │ │ ├── filter1.spec.js
│ │ │ └── filter2.js
│ │ │ └── filter2.spec.js
│ │ └── services
│ │ ├── service1.js
│ │ ├── service1.spec.js
│ │ └── service2.js
│ │ └── service2.spec.js
│ └── about
│ ├── controllers
│ │ └── ThirdCtrl.js
│ │ └── ThirdCtrl.spec.js
│ ├── directives
│ │ ├── directive2.js
│ │ ├── directive2.spec.js
│ │ └── directive3.js
│ │ └── directive3.spec.js
│ ├── filters
│ │ └── filter3.js
│ │ └── filter3.spec.js
│ └── services
│ └── service3.js
│ └── service3.spec.js
├── partials
├── lib
└── e2e-tests
- 如果目录名称包含多个单词,请使用lisp-case语法:
app
├── app.js
└── my-complex-module
├── controllers
├── directives
├── filters
└── services
- 将与给定指令(IE模板,CSS/SASS文件,JavaScript)关联的所有文件放在一个文件夹中。如果您选择使用此样式,则在项目中到处都有它。
app
└── directives
├── directive1
│ ├── directive1.html
│ ├── directive1.js
│ ├── directive1.spec.js
│ └── directive1.sass
└── directive2
├── directive2.html
├── directive2.js
├── directive2.spec.js
└── directive2.sass
该方法可以与上面的两个目录结构结合使用。
- 给定组件(
*.spec.js)的单元测试应位于组件所在的目录中。这样,当您更改给定组件时,找到其测试就很容易。测试还充当文档并显示了用例。
services
├── cache
│ ├── cache1.js
│ └── cache1.spec.js
└── models
├── model1.js
└── model1.spec.js
-
app.js文件应包含路由定义,配置和/或手动引导程序(如果需要)。 - 每个JavaScript文件只能容纳一个组件。该文件应命名为组件的名称。
- 使用AngularJS项目结构模板,例如yeoman,ng-boilerplate。
有关组件命名的约定可以在每个组件部分中找到。
标记
tldr;将脚本放在底部。
<!DOCTYPE html >
< html lang =" en " >
< head >
< meta charset =" utf-8 " >
< title > MyApp </ title >
</ head >
< body >
< div ng-app =" myApp " >
< div ng-view > </ div >
</ div >
< script src =" angular.js " > </ script >
< script src =" app.js " > </ script >
</ body >
</ html >保持简单,并在标准属性之后放置AngularJS特定指令。这将使您更容易浏览您的代码,并可以更轻松地维护,因为您的属性始终是分组和定位的。
< form class =" frm " ng-submit =" login.authenticate() " >
< div >
< input class =" ipt " type =" text " placeholder =" name " require ng-model =" user.name " >
</ div >
</ form >其他HTML属性应遵循代码指南的建议
命名约定
下表显示了每个元素的命名约定:
| 元素 | 命名风格 | 例子 | 用法 |
|---|---|---|---|
| 模块 | 下层木制 | AngularApp | |
| 控制器 | 功能 +'ctrl' | adminctrl | |
| 指令 | 下层木制 | USERINFO | |
| 过滤器 | 下层木制 | UserFilter | |
| 服务 | Uppercamelcase | 用户 | 构造函数 |
| 工厂 | 下层木制 | dataFactory | 其他的 |
其他的
- 使用:
-
$timeout而不是setTimeout -
$interval而不是setInterval -
$window而不是window -
$document而不是document -
$http而不是$.ajax -
$location而不是window.location或$window.location -
$cookies而不是document.cookie
-
这将使您的测试更加容易,在某些情况下可以防止意外行为(例如,如果您错过了$scope.$apply setTimeout
-
使用以下工具自动化工作流程:
- NPM
- 咕unt
- g
- 约曼
- 鲍尔
使用承诺(
$q)而不是回调。这将使您的代码看起来更优雅和干净,并使您免于回调地狱。尽可能使用
$resource代替$http。较高的抽象水平将使您免于冗余。使用AngularJS预麦片(NG-Antotate)预防缩小后问题。
不要使用全球。使用依赖注入解决所有依赖关系,这将防止在测试时进行错误和猴子修补。
-
通过使用Grunt/Gulp将代码包裹在立即调用功能表达式(IIFE)中,避免全球范围。您可以为此目的使用诸如Grunt-wrap或Gulp-wrap之类的插件。示例(使用Gulp)
gulp . src ( "./src/*.js" ) . pipe ( wrap ( '(function(){\n"use strict";\n<%= contents %>\n})();' ) ) . pipe ( gulp . dest ( "./dist" ) ) ;
不要污染您的
$scope。仅添加模板中使用的函数和变量。-
更喜欢使用控制器而不是
ngInit。 NGINIT只有少数适当的用途,例如用于ngrepeat的特殊属性,以及通过服务器侧脚本注入数据。除了这几种情况外,您还应使用控制器而不是nginit来初始化范围上的值。传递给ngInit的表达应通过$parse服务中实现的角度解释器进行链接,解析和评估。这导致:- 性能影响,因为解释器是在JavaScript中实施的
- 在大多数情况下,
$parse服务中解析的表达式的缓存并没有很多意义,因为ngInit表达式通常仅评估一次 - 容易出错,因为您在模板中编写字符串,因此编辑没有语法突出显示和进一步支持
- 没有运行时间错误
请勿将
$前缀用于变量,属性和方法的名称。该前缀保留用于AngularJS使用情况。请勿在您的应用程序中使用
JQUERY,如果需要的话,请使用JQLite代替angular.element。当通过AngularJs的DI机制解决依赖性时,请按其类型对依赖项进行排序 - 内置的AngularJS依赖性应首先,其次是您的自定义依赖项:
module . factory ( 'Service' , function ( $rootScope , $timeout , MyCustomDependency1 , MyCustomDependency2 ) {
return {
//Something
} ;
} ) ;模块
-
模块应用下层命名。为了指示模块
b是模块a的子模块A,您可以使用命名区域如下:ab嵌套它们。结构模块有两种常见方法:
- 通过功能
- 按组件类型
目前没有很大的区别,但是第一种方式看起来更干净。同样,如果实现了懒惰的负载模块(目前不在AngularJS路线图中),它将改善应用程序的性能。
控制器
不要在控制器中操纵DOM,这将使您的控制器更难进行测试,并违反关注原则的分离。改用指令。
控制器的命名是使用控制器的功能(例如购物车,主页,管理面板)和子字符串
Ctrl完成的。控制器是普通的JavaScript构造函数,因此它们将被命名为UpperCamelcase(
HomePageCtrl,ShoppingCartCtrl,AdminPanelCtrl等)。控制器不应定义为全球群体(即使Angularjs允许这一点,也是污染全局名称空间的不良做法)。
-
使用以下语法来定义控制器:
function MyCtrl ( dependency1 , dependency2 , ... , dependencyn ) { // ... } module . controller ( 'MyCtrl' , MyCtrl ) ;
为了防止缩小问题,您可以使用NG-Annotate(和Grunt Task grunt-ng-antotate)等工具自动从标准单元中生成数组定义语法。
另一种选择是使用
$inject:angular . module ( 'app' ) . controller ( 'HomepageCtrl' , HomepageCtrl ) ; HomepageCtrl . $inject = [ '$log' , '$http' , 'ngRoute' ] ; function HomepageCtrl ( $log , $http , ngRoute ) { // ... }
避免使用
$scope服务将功能和属性定义为控制器的一部分。仅在真正需要的情况下使用$scope:0。对于发布和订阅事件:$scope.$emit,$scope.$broadcast和$scope.$on。 0。对于手表值或收集:$scope.$watch,$scope.$watchCollection-
更喜欢使用
controller as语法,并使用变量捕获this:< div ng-controller =" MainCtrl as main " > {{ main.things }} </ div >
app . controller ( 'MainCtrl' , MainCtrl ) ; MainCtrl . $inject = [ '$http' ] ; function MainCtrl ( $http ) { var vm = this ; //a clearer visual connection on how is defined on the view vm . title = 'Some title' ; vm . description = 'Some description' ; $http . get ( '/api/main/things' ) . then ( function ( response ) { vm . things = response . data . things ; // Adding 'things' as a property of the controller } ) ; }
避免在控制器中反复使用
this关键字:app . controller ( 'MainCtrl' , MainCtrl ) ; MainCtrl . $inject = [ '$http' ] ; // Avoid function MainCtrl ( $http ) { this . title = 'Some title' ; this . description = 'Some description' ; $http . get ( '/api/main/things' ) . then ( function ( response ) { // Warning! 'this' is in a different context here. // The property will not be added as part of the controller context this . things = response . data . things ; } ) ; }
首选使用一致且短的变量名称,例如
vm。使用此语法的主要好处:
- 创建一个“隔离”组件 - 曲线属性不是
$scope原型链的一部分。这是一个很好的做法,因为$scope原型继承具有一些主要缺点(这可能是在Angular 2上删除的原因):- 很难跟踪数据的来源。
- 范围的价值变化会影响您不打算影响的地方。
- 更难重构。
- “点规则”。
- 在不需要特殊操作时删除
$scope的使用(如上所述)。这是AngularJS V2的良好准备。 - 语法更接近“香草” JavaScript构造函数
将更多的
controller as:挖掘 - ininto-angulars-controller-as syntax - 创建一个“隔离”组件 - 曲线属性不是
-
如果使用数组定义语法,请使用控制器依赖项的原始名称。这将帮助您生成更可读的代码:
function MyCtrl ( l , h ) { // ... } module . controller ( 'MyCtrl' , [ '$log' , '$http' , MyCtrl ] ) ;
比可读性不那么可读:
function MyCtrl ( $log , $http ) { // ... } module . controller ( 'MyCtrl' , [ '$log' , '$http' , MyCtrl ] ) ;
这尤其适用于具有如此多代码的文件,您需要滚动。这可能会导致您忘记哪个变量与哪个依赖关系相关。
使控制器尽可能瘦。抽象常用的功能中的功能。
-
避免在控制器内部编写业务逻辑。使用服务将业务逻辑委托给
model。例如://This is a common behaviour (bad example) of using business logic inside a controller. angular . module ( 'Store' , [ ] ) . controller ( 'OrderCtrl' , function ( ) { var vm = this ; vm . items = [ ] ; vm . addToOrder = function ( item ) { vm . items . push ( item ) ; //-->Business logic inside controller } ; vm . removeFromOrder = function ( item ) { vm . items . splice ( vm . items . indexOf ( item ) , 1 ) ; //-->Business logic inside controller } ; vm . totalPrice = function ( ) { return vm . items . reduce ( function ( memo , item ) { return memo + ( item . qty * item . price ) ; //-->Business logic inside controller } , 0 ) ; } ; } ) ;
当将业务逻辑委派成“模型”服务时,控制器将看起来像这样(请参阅“将服务作为您的模型”进行服务模型实现):
// order is used as a 'model' angular . module ( 'Store' , [ ] ) . controller ( 'OrderCtrl' , function ( order ) { var vm = this ; vm . items = order . items ; vm . addToOrder = function ( item ) { order . addToOrder ( item ) ; } ; vm . removeFromOrder = function ( item ) { order . removeFromOrder ( item ) ; } ; vm . totalPrice = function ( ) { return order . total ( ) ; } ; } ) ;
为什么控制器内部的业务逻辑 /应用状态不好?
- 控制器实例化了每个视图并在视图卸载时死亡
- 控制器不可重复使用 - 它们与视图相结合
- 控制器不应该注入
使用方法调用(当孩子想要与父母进行通信)或
$emit,$broadcast和$on方法在不同控制器中进行通信。发射和广播的消息应保持在最低限度。-
列出所有使用
$emit,$broadcast和管理的消息,因为名称碰撞和可能的错误。例子:
// app.js /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Custom events: - 'authorization-message' - description of the message - { user, role, action } - data format - user - a string, which contains the username - role - an ID of the role the user has - action - specific action the user tries to perform * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
-
当您需要格式化数据将格式逻辑封装到过滤器中并将其声明为依赖项时:
function myFormat ( ) { return function ( ) { // ... } ; } module . filter ( 'myFormat' , myFormat ) ; function MyCtrl ( $scope , myFormatFilter ) { // ... } module . controller ( 'MyCtrl' , MyCtrl ) ;
-
如果嵌套控制器使用“嵌套范围”(
controllerAs语法):app.js
module . config ( function ( $routeProvider ) { $routeProvider . when ( '/route' , { templateUrl : 'partials/template.html' , controller : 'HomeCtrl' , controllerAs : 'home' } ) ; } ) ;
homectrl
function HomeCtrl ( ) { var vm = this ; vm . bindingValue = 42 ; }
template.html
< div ng-bind =" home.bindingValue " > </ div >
指令
- 用LowerCamelcase命名您的指示。
- 在链接功能中使用
scope而不是$scope。在编译中,您已经定义的参数已定义了,该参数将在调用函数时通过,您将无法使用DI更改它们。此样式也用于AngularJS的源代码。 - 使用自定义前缀为您的指令,以防止名称与第三方库的碰撞。
- 请勿使用
ng或ui前缀,因为它们是用于AngularJS和AngularJS UI使用的。 - DOM操作必须仅通过指令进行。
- 当您开发可重复使用的组件时,创建一个孤立的范围。
- 将指令用作属性或元素,而不是注释或类,这将使您的代码更可读。
- 使用
scope.$on('$destroy', fn)进行清理。将第三方插件作为指令,这一点特别有用。 - 当您应该处理不信任的内容时,请不要忘记使用
$sce。
过滤器
- 用LowerCamelcase命名您的过滤器。
- 使您的过滤器尽可能轻。它们在
$digest循环期间经常被调用,因此创建缓慢的过滤器会减慢您的应用程序。 - 在过滤器中做一件事情,让它们连贯。可以通过管道现有过滤器来实现更复杂的操作。
服务
本节包括有关AngularJS中服务组件的信息。它不取决于定义方式(即作为提供者, .factory , .service ),除非明确提及。
-
使用骆驼命名您的服务。
-
用于命名您的服务的UpperCamelcase(Pascalcase),用作构造函数函数,即:
function MainCtrl ( User ) { var vm = this ; vm . user = new User ( 'foo' , 42 ) ; } module . controller ( 'MainCtrl' , MainCtrl ) ; function User ( name , age ) { this . name = name ; this . age = age ; } module . factory ( 'User' , function ( ) { return User ; } ) ;
所有其他服务的下层。
-
-
将所有业务逻辑封装在服务中。希望将其用作
model。例如:// order is the 'model' angular . module ( 'Store' ) . factory ( 'order' , function ( ) { var add = function ( item ) { this . items . push ( item ) ; } ; var remove = function ( item ) { if ( this . items . indexOf ( item ) > - 1 ) { this . items . splice ( this . items . indexOf ( item ) , 1 ) ; } } ; var total = function ( ) { return this . items . reduce ( function ( memo , item ) { return memo + ( item . qty * item . price ) ; } , 0 ) ; } ; return { items : [ ] , addToOrder : add , removeFromOrder : remove , totalPrice : total } ; } ) ;
有关耗资此服务的控制器的示例,请参见“避免在控制器内部编写业务逻辑”。
-
代表该域的服务优选是
service而不是factory。通过这种方式,我们可以更轻松地利用“ Klassical”继承:function Human ( ) { //body } Human . prototype . talk = function ( ) { return "I'm talking" ; } ; function Developer ( ) { //body } Developer . prototype = Object . create ( Human . prototype ) ; Developer . prototype . code = function ( ) { return "I'm coding"
下载源码通过命令行克隆项目:
git clone https://github.com/mgechev/angularjs-style-guide.git