angularjs-styleguide, AngularJS styleguide用于团队

分享于 

31分钟阅读

GitHub

  繁體 雙語
Opinionated AngularJS styleguide for teams
  • 源代码名称:angularjs-styleguide
  • 源代码网址:http://www.github.com/toddmotto/angularjs-styleguide
  • angularjs-styleguide源代码文档
  • angularjs-styleguide源代码下载
  • Git URL:
    git://www.github.com/toddmotto/angularjs-styleguide.git
    Git Clone代码到本地:
    git clone http://www.github.com/toddmotto/angularjs-styleguide
    Subversion代码到本地:
    $ svn co --depth empty http://www.github.com/toddmotto/angularjs-styleguide
    Checked out revision 1.
    $ cd repo
    $ svn up trunk
    
    AngularJS styleguide ( ES2015 )

    Up-to-date 1.6最佳实践。 架构,文件结构,组件,单向数据流,生命周期钩子。

    需要一个示例结构作为参考? 查看我的基于组件的架构 1.5应用程序。

    :通过 @toddmotto,为团队提供了一个合理的styleguide。

    这个架构和styleguide已经从底层重写为 ES2015,AngularJS +的变化为将来升级你的应用程序提供了 1.5 +。 本指南包括了一种方法,以单向数据流,事件委托,组件架构和组件路由。

    这里可以找到旧的styleguide ,并在这里找到新的reasoning

    加入最终的datacontext学习体验,完全掌握初学者和高级的datacontext特性,以构建快速的,和比例。

    table-内容

    Angular 应用程序中的每个 MODULE 都是 MODULE 组件。 MODULE 组件是用于封装逻辑。模板。路由和子组件的MODULE的root 定义。

    MODULE 理论

    模块中的设计直接映射到我们的文件夹结构,这使事情保持可以维护。 理想情况下,我们应该有三个高级模块: root,组件和通用。 root MODULE 定义了引导我们应用程序的基础 MODULE,以及相应的模板。 然后我们将组件和公共模块导入 root MODULE 中,以包含我们的依赖关系。 组件和通用模块需要低级组件模块,其中包含了每个可以重用功能的组件。控制器。服务。指令。过滤器和测试。

    最高

    root-模块

    A MODULE 从定义整个应用程序的基本元素的root 组件开始,并定义了路由输出口,示例中显示了使用 ui-view的示例。

    // app.component.jsexportconstAppComponent= {
     template:` <header> Hello world </header> <div> <div ui-view></div> </div> <footer> Copyright MyApp 2016. </footer>`};

    然后创建一个 root MODULE,并将 AppComponent 导入并注册为 .component('app', AppComponent)。 子模块( 组件和公共模块) 进一步导入,包括与应用程序相关的所有组件。 你将注意到在这里也进入了样式,我们将在本指南后面的章节中进行。

    // app.module.jsimportangularfrom'angular';importuiRouterfrom'angular-ui-router';import { AppComponent } from'./app.component';import { ComponentsModule } from'./components/components.module';import { CommonModule } from'./common/common.module';import'./app.scss';exportconstAppModule= angular
    . module('app', [
     ComponentsModule,
     CommonModule,
     uiRouter
     ])
    . component('app', AppComponent)
    . name;

    最高

    组件模块

    组件 MODULE 是所有可以重用组件的容器引用。 查看 上面 如何导入 ComponentsModule 并将它的注入 root MODULE,这为我们提供了一个单独的位置来导入应用程序的所有组件。 我们要求这些模块与所有其他模块分离,因此可以轻松地转移到任何其他应用程序中。

    importangularfrom'angular';import { CalendarModule } from'./calendar/calendar.module';import { EventsModule } from'./events/events.module';exportconstComponentsModule= angular
    . module('app.components', [
     CalendarModule,
     EventsModule
     ])
    . name;

    最高

    通用模块

    通用 MODULE 是所有特定组件的容器引用,我们不希望在其他应用程序中使用。 这可以是布局,导航和页脚之类的东西。 查看 上面 如何导入 CommonModule 并将它们注入 root MODULE,这为应用程序导入所有通用组件提供了一个地方。

    importangularfrom'angular';import { NavModule } from'./nav/nav.module';import { FooterModule } from'./footer/footer.module';exportconstCommonModule= angular
    . module('app.common', [
     NavModule,
     FooterModule
     ])
    . name;

    最高

    低级别模块

    低级模块是包含每个功能 block的逻辑的单个组件模块。 这些将定义一个 MODULE,将它的导入到更高级别的MODULE,例如组件或者公共 MODULE,例如 below。 始终记得在创建新的时将 .name 后缀添加到每个 export,而不是在引用。 我们将在这里发现路由定义,我们将在本指南的后面章节中对此进行讨论。

    importangularfrom'angular';importuiRouterfrom'angular-ui-router';import { CalendarComponent } from'./calendar.component';import'./calendar.scss';exportconstCalendarModule= angular
    . module('calendar', [
     uiRouter
     ])
    . component('calendar', CalendarComponent)
    . config(($stateProvider, $urlRouterProvider) => {
     'ngInject';
     $stateProvider
    . state('calendar', {
     url:'/calendar',
     component:'calendar' });
     $urlRouterProvider.otherwise('/');
     })
    . name;

    最高

    文件命名约定

    保持简单和 lowercase,使用组件 NAME,比如 calendar.*.js*calendar-grid.*.js - 与中间的文件类型的NAME。 对 MODULE 定义文件使用 *.module.js,因为它使它与 Angular 保持冗长和一致。

    
    calendar.module.js
    
    
    calendar.component.js
    
    
    calendar.service.js
    
    
    calendar.directive.js
    
    
    calendar.filter.js
    
    
    calendar.spec.js
    
    
    calendar.html
    
    
    calendar.scss
    
    
    
    

    最高

    可以扩展文件结构

    文件结构非常重要,它描述了一个可以扩展的可以预测的结构。 一个范例文件结构来说明模块化组件。

    
    ├── app/
    
    
    │ ├── components/
    
    
    │ │ ├── calendar/
    
    
    │ │ │ ├── calendar.module.js
    
    
    │ │ │ ├── calendar.component.js
    
    
    │ │ │ ├── calendar.service.js
    
    
    │ │ │ ├── calendar.spec.js
    
    
    │ │ │ ├── calendar.html
    
    
    │ │ │ ├── calendar.scss
    
    
    │ │ │ └── calendar-grid/
    
    
    │ │ │ ├── calendar-grid.module.js
    
    
    │ │ │ ├── calendar-grid.component.js
    
    
    │ │ │ ├── calendar-grid.directive.js
    
    
    │ │ │ ├── calendar-grid.filter.js
    
    
    │ │ │ ├── calendar-grid.spec.js
    
    
    │ │ │ ├── calendar-grid.html
    
    
    │ │ │ └── calendar-grid.scss
    
    
    │ │ ├── events/
    
    
    │ │ │ ├── events.module.js
    
    
    │ │ │ ├── events.component.js
    
    
    │ │ │ ├── events.directive.js
    
    
    │ │ │ ├── events.service.js
    
    
    │ │ │ ├── events.spec.js
    
    
    │ │ │ ├── events.html
    
    
    │ │ │ ├── events.scss
    
    
    │ │ │ └── events-signup/
    
    
    │ │ │ ├── events-signup.module.js
    
    
    │ │ │ ├── events-signup.component.js
    
    
    │ │ │ ├── events-signup.service.js
    
    
    │ │ │ ├── events-signup.spec.js
    
    
    │ │ │ ├── events-signup.html
    
    
    │ │ │ └── events-signup.scss
    
    
    │ │ └── components.module.js
    
    
    │ ├── common/
    
    
    │ │ ├── nav/
    
    
    │ │ │ ├── nav.module.js
    
    
    │ │ │ ├── nav.component.js
    
    
    │ │ │ ├── nav.service.js
    
    
    │ │ │ ├── nav.spec.js
    
    
    │ │ │ ├── nav.html
    
    
    │ │ │ └── nav.scss
    
    
    │ │ ├── footer/
    
    
    │ │ │ ├── footer.module.js
    
    
    │ │ │ ├── footer.component.js
    
    
    │ │ │ ├── footer.service.js
    
    
    │ │ │ ├── footer.spec.js
    
    
    │ │ │ ├── footer.html
    
    
    │ │ │ └── footer.scss
    
    
    │ │ └── common.module.js
    
    
    │ ├── app.module.js
    
    
    │ ├── app.component.js
    
    
    │ └── app.scss
    
    
    └── index.html
    
    
    
    

    高级文件夹结构简单地包含 index.htmlapp/,它的中所有的root。组件。公共和低级模块都与标记和样式。

    最高

    组件

    组件理论

    组件本质上是带有控制器的模板。 这些命令不是基于组件的指令,也不是使用组件替换指令,除非使用控制器来升级"模板指令",这最适合作为组件。 组件还包含定义数据和事件的输入和输出,生命周期挂钩和使用一种方法数据流的绑定。 这些是 AngularJS 1.5和 上面 中新的事实标准。 我们创建的模板和控制器都可能是一个组件,它可能是一个有状态的。无状态的或者路由的组件。 可以将"组件"看作一个完整的代码 Fragment,而不仅仅是 .component() 定义对象。 让我们探索一些组件的最佳实践和建议,然后深入探讨如何通过有状态。无状态和路由组件概念构造它们。

    最高

    支持的属性

    以下是你可以/应使用的.component()的受支持属性:

    属性支持
    绑定是,仅使用 '@''<''&'
    控制器是的
    controllerAs是,默认值为 $ctrl
    要求是( 新对象语法)
    模板是的
    templateUrl是的
    transclude是的

    最高

    控制器

    控制器应该只与组件一起使用,不应该在其他地方使用。 如果你觉得需要控制器,那么真正需要的是一个无状态组件来管理这个特定的行为。

    以下是有关使用 Class 进行控制器的一些建议:

    • 删除 NAME"控制器",换句话说,使用 controller: class TodoComponent {...} 帮助将来的Angular 迁移
    • 始终将 constructor 用于依赖项注入目
    • 使用 ng注释'ngInject';的语法进行 $inject 注释
    • 如果需要访问词法范围,请使用箭头函数
    • 或者是箭头函数 let ctrl = this; 也可以接受,根据使用情况可能更有意义
    • 将所有 public 函数直接绑定到 Class
    • 使用适当的生命周期钩子。$onInit$onChanges$postLink$onDestroy
      • 注意:在 $onInit 之前调用 $onChanges,有关详细信息,请参阅资源部分
    • $onInit 旁边使用 require 来引用任何继承的逻辑
    • 不要重写 controllerAs 语法的默认 $ctrl 别名,因此不要在任何地方使用 controllerAs

    最高

    单向数据流和事件

    AngularJS 1.5引入了一种方法,并重新定义了组件通信。

    以下是使用单向数据流的一些建议:

    • 在接收数据的组件中,始终使用单向数据绑定语法 '<'
    • 现在,不再使用 '=' 两种方法语法数据绑定语法
    • 拥有 bindings的组件应该使用 $onChanges 来克隆单向绑定数据以打断通过引用传递的对象并更新父数据。
    • 在父方法中使用 $event 作为函数参数( 请参见状态化示例 below $ctrl.addTodo($event) )
    • 从无状态组件( 请参阅无状态示例 below this.onAddTodo ) 中传递 $event: {} 对象。
      • 加成:使用带有 .value()EventEmitter 包装来镜像 Angular,避免手工创建 $event 对象
    • 为什么这个镜像 Angular 并保持一致性 inside的一致性。 它也使状态可以预测。

    最高

    有状态组件

    我们来定义一下我们称之为"有状态组件"的东西。

    • 获取状态,本质上通过服务与后端API通信
    • 不直接突变状态
    • 呈现变异状态的子组件
    • 也称为智能/容器组件

    有状态组件的一个示例,它的低级 MODULE 定义( 这只是为了演示,为了简洁起见,省略了一些代码) 为:

    /* ----- todo/todo.component.js ----- */importtemplateUrlfrom'./todo.html';exportconstTodoComponent= {
     templateUrl,
     controller:classTodoComponent {
     constructor(TodoService) {
     'ngInject';
     this.todoService= TodoService;
     }
     $onInit() {
     this.newTodo= {
     title:'',
     selected:false };
     this.todos= [];
     this.todoService.getTodos().then(response=>this.todos= response);
     }
     addTodo({ todo }) {
     if (!todo) return;
     this.todos.unshift(todo);
     this.newTodo= {
     title:'',
     selected:false };
     }
     }
    };/* ----- todo/todo.html ----- */<div class="todo"><todo-form
     todo="$ctrl.newTodo" on-add-todo="$ctrl.addTodo($event);"></todo-form><todo-list
     todos="$ctrl.todos"></todo-list></div>/* ----- todo/todo.module.js ----- */importangularfrom'angular';import { TodoComponent } from'./todo.component';import'./todo.scss';exportconstTodoModule= angular
    . module('todo', [])
    . component('todo', TodoComponent)
    . name;

    这个例子展示了一个有状态组件,它通过一个服务获取状态 inside,并通过一个服务,然后将它传递到无状态子组件。 注意如何在模板中使用 ng-repeat 和朋友 inside 之类的指令。 相反,数据和函数被委派给 <todo-form><todo-list> 无状态组件。

    最高

    无状态组件

    我们来定义一下我们称之为"无状态组件"的东西。

    • 使用 bindings: {} 定义输入和输出
    • 数据通过属性绑定进入组件( 输入)
    • 数据通过事件离开组件( 输出)
    • 改变状态,按需传递数据( 如单击或者提交事件)
    • 不关心数据来自哪里- 它是无状态
    • 是高度可以重用的组件
    • 也被称为哑/表示组件

    一个无状态组件( 让我们用 <todo-form> 作为例子)的示例,它的底层 MODULE 定义( 这只是为了演示,为了简洁起见,省略了一些代码) 为:

    /* ----- todo/todo-form/todo-form.component.js ----- */importtemplateUrlfrom'./todo-form.html';exportconstTodoFormComponent= {
     bindings: {
     todo:'<',
     onAddTodo:'&' },
     templateUrl,
     controller:classTodoFormComponent {
     constructor(EventEmitter) {
     'ngInject';
     this.EventEmitter= EventEmitter;
     }
     $onChanges(changes) {
     if (changes.todo) {
     this.todo=Object.assign({}, this.todo);
     }
     }
     onSubmit() {
     if (!this.todo.title) return;
     // with EventEmitter wrapperthis.onAddTodo(
     this.EventEmitter({
     todo:this.todo })
     );
     // without EventEmitter wrapperthis.onAddTodo({
     $event: {
     todo:this.todo }
     });
     }
     }
    };/* ----- todo/todo-form/todo-form.html ----- */<form name="todoForm" ng-submit="$ctrl.onSubmit();"><input type="text" ng-model="$ctrl.todo.title"><button type="submit">Submit</button></form>/* ----- todo/todo-form/todo-form.module.js ----- */importangularfrom'angular';import { TodoFormComponent } from'./todo-form.component';import'./todo-form.scss';exportconstTodoFormModule= angular
    . module('todo.form', [])
    . component('todoForm', TodoFormComponent)
    . value('EventEmitter', payload=> ({ $event: payload }))
    . name;

    请注意,<todo-form> 组件如何获取它,它只是接收它,通过与它关联的控制器逻辑将它传递回父组件。 在这个例子中,$onChanges 生命周期钩子复制初始的this.todo 绑定对象并重新分配它,这意味着父数据不会受到影响,这意味着在提交表单的同时,数据流新绑定语法将被。

    最高

    路由组件

    我们来定义一下我们称之为"路由组件"的东西。

    • 它本质上是一个有状态组件,
    • router.js 文件
    • 我们使用路由组件来定义他们自己的路由逻辑
    • 组件的数据"输入"通过路由解析( 可选,仍然在带有服务调用的控制器中可用) 完成

    对于这个例子,我们将采用现有的<todo> 组件,重构它以使用在接收数据( 这里的ui-router 是我们创建的resolve 属性,在本例中,todoData 直接映射到 bindings。)的组件上的路由定义和 bindings。 我们将它视为路由组件,因为它本质上是一个"视图":

    /* ----- todo/todo.component.js ----- */importtemplateUrlfrom'./todo.html';exportconstTodoComponent= {
     bindings: {
     todoData:'<' },
     templateUrl,
     controller:classTodoComponent {
     constructor() {
     'ngInject'; // Not actually needed but best practice to keep here incase dependencies needed in the future }
     $onInit() {
     this.newTodo= {
     title:'',
     selected:false };
     }
     $onChanges(changes) {
     if (changes.todoData) {
     this.todos=Object.assign({}, this.todoData);
     }
     }
     addTodo({ todo }) {
     if (!todo) return;
     this.todos.unshift(todo);
     this.newTodo= {
     title:'',
     selected:false };
     }
     }
    };/* ----- todo/todo.html ----- */<div class="todo"><todo-form
     todo="$ctrl.newTodo" on-add-todo="$ctrl.addTodo($event);"></todo-form><todo-list
     todos="$ctrl.todos"></todo-list></div>/* ----- todo/todo.service.js ----- */exportclassTodoService {
     constructor($http) {
     'ngInject';
     this.$http= $http;
     }
     getTodos() {
     returnthis.$http.get('/api/todos').then(response=>response.data);
     }
    }/* ----- todo/todo.module.js ----- */importangularfrom'angular';importuiRouterfrom'angular-ui-router';import { TodoComponent } from'./todo.component';import { TodoService } from'./todo.service';import'./todo.scss';exportconstTodoModule= angular
    . module('todo', [
     uiRouter
     ])
    . component('todo', TodoComponent)
    . service('TodoService', TodoService)
    . config(($stateProvider, $urlRouterProvider) => {
     'ngInject';
     $stateProvider
    . state('todos', {
     url:'/todos',
     component:'todo',
     resolve: {
     todoData:TodoService=>TodoService.getTodos()
     }
     });
     $urlRouterProvider.otherwise('/');
     })
    . name;

    最高

    指令

    指导理论

    指令给我们 templatescope 绑定。bindToControllerlink 和许多其他东西。 现在 .component() 存在时,应该仔细考虑这些用法。 指令不应再声明模板和控制器,也不能通过绑定接收数据。 指令应单独用于修饰 DOM。 由此,它意味着扩展用 .component() 创建的现有 HTML。 简单地说,如果需要自定义DOM事件/api和逻辑,请使用指令并将它的绑定到模板 inside。 如果需要有理想的DOM操作,也可以考虑使用 $postLink 生命周期钩子。

    以下是有关使用指令的一些建议:

    • 从不使用模板,作用域,bindToController或者控制器
    • 始终带有指令的restrict:'A'
    • 在必要时使用编译和链接
    • 请记住销毁和取消绑定事件处理程序 inside $scope.$on('$destroy', fn);

    最高

    推荐的属性

    由于事实指令支持大多数 .component() 做( 模板指令是原始组件),我建议仅将指令对象定义限制为这些属性,以避免使用指令:

    属性使用它为什么?
    bindToController不是在组件中使用 bindings
    编译是的用于预编译DOM操作/事件
    控制器不是使用组件
    controllerAs不是使用组件
    链接函数是的用于前/后DOM操作/事件
    多元素是的请参阅文档文档
    优先级是的请参阅文档文档
    要求不是使用组件
    限制是的定义指令用法,始终使用 'A'
    作用域不是使用组件
    模板不是使用组件
    templateNamespace是( 如果必须)请参阅文档文档
    templateUrl不是使用组件
    transclude不是使用组件

    最高

    常数或者类

    通过使用箭头函数和简单的分配或者使用 ES2015 Class,有几种方法可以使用ES2015和指令。 选择最适合你的或者你的团队,记住 Angular 使用 Class

    下面是一个使用带有箭头函数的常量的示例表达式包装器 () => ({}) 返回一个对象文本( 注意使用差异 inside .directive() ):

    /* ----- todo/todo-autofocus.directive.js ----- */importangularfrom'angular';exportconstTodoAutoFocus= ($timeout) => {
     'ngInject';
     return {
     restrict:'A',
     link($scope, $element, $attrs) {
     $scope.$watch($attrs.todoAutofocus, (newValue, oldValue) => {
     if (!newValue) {
     return;
     }
     $timeout(() => $element[0].focus());
     });
     }
     }
    };/* ----- todo/todo.module.js ----- */importangularfrom'angular';import { TodoComponent } from'./todo.component';import { TodoAutofocus } from'./todo-autofocus.directive';import'./todo.scss';exportconstTodoModule= angular
    . module('todo', [])
    . component('todo', TodoComponent)
    . directive('todoAutofocus', TodoAutoFocus)
    . name;

    或者使用 ES2015 Class ( 注意在注册指令时手动调用 new TodoAutoFocus ) 创建对象:

    /* ----- todo/todo-autofocus.directive.js ----- */importangularfrom'angular';exportclassTodoAutoFocus {
     constructor($timeout) {
     'ngInject';
     this.restrict='A';
     this.$timeout= $timeout;
     }
     link($scope, $element, $attrs) {
     $scope.$watch($attrs.todoAutofocus, (newValue, oldValue) => {
     if (!newValue) {
     return;
     }
     this.$timeout(() => $element[0].focus());
     });
     }
    }/* ----- todo/todo.module.js ----- */importangularfrom'angular';import { TodoComponent } from'./todo.component';import { TodoAutofocus } from'./todo-autofocus.directive';import'./todo.scss';exportconstTodoModule= angular
    . module('todo', [])
    . component('todo', TodoComponent)
    . directive('todoAutofocus', ($timeout) =>newTodoAutoFocus($timeout))
    . name;

    最高

    电子邮件服务

    服务理论

    服务本质上是业务逻辑的容器,我们的组件不应该直接请求。 服务包含其他内置的或者外部的服务,比如 $http,我们可以将它们注入到应用程序的其他地方。 我们有两种服务方式,使用 .service() 或者 .factory()。 使用 ES2015 Class,我们应该只使用 .service(),使用 $inject 完成依赖注入注释。

    最高

    用于服务的

    下面是使用 ES2015 Class 实现 <todo> 应用程序的示例实现:

    /* ----- todo/todo.service.js ----- */exportclassTodoService {
     constructor($http) {
     'ngInject';
     this.$http= $http;
     }
     getTodos() {
     returnthis.$http.get('/api/todos').then(response=>response.data);
     }
    }/* ----- todo/todo.module.js ----- */importangularfrom'angular';import { TodoComponent } from'./todo.component';import { TodoService } from'./todo.service';import'./todo.scss';exportconstTodoModule= angular
    . module('todo', [])
    . component('todo', TodoComponent)
    . service('TodoService', TodoService)
    . name;

    最高

    样式

    在使用 Webpack 我们现在可以在 *.module.js 中的.scss 文件上使用 import 语句来让 web pack知道包含该文件到样式表中。 这样做可以使我们的组件既可以独立于功能和样式,也可以与样式表中的样式表的声明方式紧密一致。 这样做将不会将我们的样式与 Angular 类似,这样样式仍然可以用,但是它更易于理解。

    如果你有一些变量或者全局使用的样式如表单输入元素,那么这些文件仍然应该放在 root scss 文件夹中。 比如 scss/_forms.scss。这些全局样式然后可以直接在你的root MODULE ( app.module.js ) 样式表中为 @imported 样式表。

    最高

    ES2015和工具
    • 使用 编译ES2015+代码和任何 polyfills
    • 考虑使用 TypeScript 为任何 Angular 升级提供途径
    命令行工具
    • 如果你想支持组件路由,请使用 ui-router最新 alpha ( 请参阅自述文件)
      • 否则,你就会陷入 template:'<component>'bindings/resolve 映射
    • 使用 angular-templates 或者 ngtemplate-loader 将预加载模板考虑到 $templateCache
    • 考虑使用 web pack 编译ES2015代码和样式
    • 使用 ngAnnotate 自动注释 $inject 属性
    • 如何将 ngAnnotate与 ES6 一起使用

    最高

    基于的状态管理

    考虑使用Redux与 AngularJS 1.5进行数据管理。

    最高

    资源

    最高

    文档

    对于其他内容,包括API引用,检查 AngularJS文档文档。

    打开问题以讨论潜在的更改/添加。 请不要打开问题的问题。

    许可证

    ( MIT许可证)

    版权所有( c ) 2016 -2017 Todd格言

    若要在取得该软件副本时免费授予任何人,如有下列条件的软件,请免费授予该软件的副本,并与相关的文档文件('软件') 进行许可,包括不受限制的权利,包括以下条件:

    上述版权声明和本许可声明须包括在所有的副本或实质性部分的软件。

    软件是'是',没有任何保证,表示或者隐含,包括但不限于销售,适合特定用途和 NONINFRINGEMENT。 作者或者版权持有人在合同。侵权或者它的他与软件或者它的他用户交易的行为。


    angular  style  team  
    相关文章