AngularJS 作风指南 (ES2015)

# AngularJS 作风指南 (ES2015)

AngularJs1.6.x的最好实践.涵盖体系组织,文件组织,组件,单向数据流和性命周期。

目次

  1. 模块化体系组织

    1. 概述
    2. Root module
    3. Component module
    4. Common module
    5. Low-level modules
    6. 文件定名商定
    7. 可伸缩文件组织
  2. 组件

    1. 概述
    2. 支撑的属性
    3. 控制器
    4. 单项数据流和事宜
    5. 有状况组件/容器型组件
    6. 无状况组件/展现型组件
    7. 路由组件
  3. 指令

    1. 概述
    2. 引荐的属性
    3. Constants or Classes
  4. 效劳

    1. 概述
    2. Classes for Service
  5. 款式
  6. ES2015 and Tooling
  7. 状况治理

模块化体系组织

Angular运用顺序中的每一个模块都应当是组件模块。组件模块用来封装逻辑、模板、路由和子组件。

模块概述

模块的设想直接回响反映了我们的文件组织, 从而坚持了可维护性和可展望性。我们最好有三个高等模块: root、component和common。root模块是用来启动我们运用和响应模板的基本模块,root模块中导入component和common模块作为依靠模块。component和common模块则担任引入Low-level modules,Low-level modules一般包含可重用的组件,控制器,效劳,指令,过滤器和测试.

Back to top

Root 模块

root模块中定义全部运用的根组件,根组件中一般包含路由组件,比方ui-router 中的 ui-view

// app.component.js
export const AppComponent = {
  template: `
    <header>
        Hello world
    </header>
    <div>
        <div ui-view></div>
    </div>
    <footer>
        Copyright MyApp 2016.
    </footer>
  `
};
// app.module.js
import angular from 'angular';
import uiRouter from 'angular-ui-router';
import { AppComponent } from './app.component';
import { ComponentsModule } from './components/components.module';
import { CommonModule } from './common/common.module';
import './app.scss';

export const AppModule = angular
  .module('app', [
    ComponentsModule,
    CommonModule,
    uiRouter
  ])
  .component('app', AppComponent)
  .name;

运用 .component('app', AppComponent) 要领在root模块中注册根组件,你能够注重到款式也被引入到了根组件,我们将在背面的章节引见这一点.

Back to top

Components 模块

一切的可重用组件应当注册在component模块上。上面例子中展现了我们是怎样导入 ComponentsModule 并将它们注册在root模块中。如许做能够轻松的将component模块挪动就任何其他运用顺序中,因为root模块与component模块是星散的。

import angular from 'angular';
import { CalendarModule } from './calendar/calendar.module';
import { EventsModule } from './events/events.module';

export const ComponentsModule = angular
  .module('app.components', [
    CalendarModule,
    EventsModule
  ])
  .name;

Back to top

Common module

一切我们不愿望用在其他运用中的组件应当注册在common模块上。比方规划、导航和页脚之类的内容。

import angular from 'angular';
import { NavModule } from './nav/nav.module';
import { FooterModule } from './footer/footer.module';

export const CommonModule = angular
  .module('app.common', [
    NavModule,
    FooterModule
  ])
  .name;

Back to top

Low-level modules

Low-level module是包含自力功用的组件模块,将会被导入到像component module或许是 common module如许的higher-level module中,下面是一个例子。一定要记住在每一个export的模块末了增加.name。你能够注重到路由定义也在这个模块中,我们将在本指南背面的章节中引见这一点。

import angular from 'angular';
import uiRouter from 'angular-ui-router';
import { CalendarComponent } from './calendar.component';
import './calendar.scss';

export const CalendarModule = angular
  .module('calendar', [
    uiRouter
  ])
  .component('calendar', CalendarComponent)
  .config(($stateProvider, $urlRouterProvider) => {
    'ngInject';
    $stateProvider
      .state('calendar', {
        url: '/calendar',
        component: 'calendar'
      });
    $urlRouterProvider.otherwise('/');
  })
  .name;

Back to top

文件定名商定

坚持文件名简朴,而且运用小写字母,文件名运用 ‘ – ‘支解,比方 calendar-grid.*.js 。运用 *.component.js 标示组件,运用 *.module.js 标示模块

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

Back to top

可伸缩文件组织

文件组织是非常重要的,这形貌了一个可伸缩和可展望的组织,下面是一个例子。

├── 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.html and app/,其他的模块在app/

Back to top

组件

组件概述

组件是带有控制器的模板,组件能够经由过程 bindings 定义数据或是事宜的输入和输出,你能够将组件视为完全的代码段,而不仅仅是 .component() 定义的对象,让我们讨论一些关于组件的最好实践和发起, 然后深切相识怎样经由过程有状况的、无状况的和路由组件的观点来组织它们。

Back to top

支撑的属性

这些是 .component() 支撑的属性

PropertySupport
bindingsYes, use '@', '<', '&' only
controllerYes
controllerAsYes, default is $ctrl
requireYes (new Object syntax)
templateYes
templateUrlYes
transcludeYes

Back to top

控制器

控制器应当只和组件一同运用,假如你觉得须要一个控制器,能够你须要的是一个治理这个特定行动的组件。

这是一些运用 Class 定义controllers的发起:

  • 运用 controller: class TodoComponent {...} 这类写法以应对未来向Angular迁徙
  • 一直运用 constructor 来举行依靠注入
  • 运用 babel-plugin-angularjs-annotate'ngInject';语法
  • 假如须要接见词法作用域,请运用箭头函数
  • 除了箭头函数 let ctrl = this; 也是能够吸收的
  • 将一切的大众函数绑定到 class{}
  • 恰当的运用 $onInit, $onChanges, $postLink and $onDestroy 等性命周期函数
  • Note: $onChanges is called before $onInit, see resources section for articles detailing this in more depth
  • Use require alongside $onInit to reference any inherited logic
  • Do not override the default $ctrl alias for the controllerAs syntax, therefore do not use controllerAs anywhere

Back to top

单向数据流和事宜

AngularJS 1.5中引入了单向数据流,这从新定义了组件之间的通讯.

这里有一些运用单向数据流的发起:

  • 在组件中吸收数据时,老是运用单向数据绑定语法 '<'
  • 任何时候都不要在运用双向绑定语法 '='
  • 运用 bindings 绑定传入数据时,在 $onChanges 性命周期函数中深复制传入的对象以消除父组件的援用
  • 在父组件的要领中运用 $event 作为函数的参数(检察下面有状况组件的例子$ctrl.addTodo($event))
  • 从无状况组件的要领中通报回 $event: {} 对象(检察下面的无状况组件例子 this.onAddTodo)

<!– * Bonus: Use an EventEmitter wrapper with .value() to mirror Angular, avoids manual $event Object creation

  • Why? This mirrors Angular and keeps consistency inside every component. It also makes state predictable. –>

Back to top

有状况组件

让我们定义一个有状况组件

  • 猎取状况,实质上是经由过程效劳与背景API通讯
  • 不要直接变化状况

<!– * Renders child components that mutate state

  • Also referred to as smart/container components –>

一个包含low-level module定义的有状况组件的例子(为了简约省略了一些代码)

/* ----- todo/todo.component.js ----- */
import templateUrl from './todo.html';

export const TodoComponent = {
  templateUrl,
  controller: class TodoComponent {
    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 ----- */
import angular from 'angular';
import { TodoComponent } from './todo.component';
import './todo.scss';

export const TodoModule = angular
  .module('todo', [])
  .component('todo', TodoComponent)
  .name;

这个例子展现了一个有状况组件,在控制器中经由过程效劳猎取数据,然后通报给无状况子组件。注重我们在模板中并没有运用ng-repeat 之类的指令,而是托付给 <todo-form><todo-list>组件

Back to top

无状况组件

让我们第一我们所谓的无状况组件:

  • 运用 bindings: {} 明白的定义输入和输出
  • 经由过程属性绑定通报数据
  • 数据经由过程事宜脱离组件

<!– * Mutates state, passes data back up on-demand (such as a click or submit event) –>

  • 不要体贴数据来自那里 – 它是无状况的
  • 是高度可重用的组件
  • 也被称为展现型组件

一个无状况组件的例子( <todo-form> 为了简约省略了一些代码)

/* ----- todo/todo-form/todo-form.component.js ----- */
import templateUrl from './todo-form.html';

export const TodoFormComponent = {
  bindings: {
    todo: '<',
    onAddTodo: '&'
  },
  templateUrl,
  controller: class TodoFormComponent {
    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 wrapper
      this.onAddTodo(
        this.EventEmitter({
          todo: this.todo
        })
      );
      // without EventEmitter wrapper
      this.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 ----- */
import angular from 'angular';
import { TodoFormComponent } from './todo-form.component';
import './todo-form.scss';

export const TodoFormModule = angular
  .module('todo.form', [])
  .component('todoForm', TodoFormComponent)
  .value('EventEmitter', payload => ({ $event: payload }))
  .name;

注重, <todo-form> 组件没有状况,仅仅是吸收数据,经由过程属性绑定的事宜通报数据回到父组件。上例中,在 $onChanges 函数内深复制了 this.todo ,这意味着在提交回父组件前,父组件的数据是不受影响的,

Back to top

路由组件

让我们定义一个路由组件。

  • 一个带有路由定义的有状况组件
  • 没有 router.js 文件
  • 运用路由组件来定义他们本身的路由逻辑
  • 组件数据的输入是经由过程路由的 resolve

在这个例子中,我们将运用路由定义和 bindings重构 <todo> 组件.我们将其视为路由组件,因为它本质上是一个”视图”

/* ----- todo/todo.component.js ----- */
import templateUrl from './todo.html';

export const TodoComponent = {
  bindings: {
    todoData: '<'
  },
  templateUrl,
  controller: class TodoComponent {
    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 ----- */
export class TodoService {
  constructor($http) {
    'ngInject';
    this.$http = $http;
  }
  getTodos() {
    return this.$http.get('/api/todos').then(response => response.data);
  }
}

/* ----- todo/todo.module.js ----- */
import angular from 'angular';
import uiRouter from 'angular-ui-router';
import { TodoComponent } from './todo.component';
import { TodoService } from './todo.service';
import './todo.scss';

export const TodoModule = 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;

Back to top

指令

指令概述

指令有 template, scope 绑定, bindToController, link 和许多其他特征。在基于组件的运用中应当注重他们的运用。指令不应再声明模板和控制器,或许经由过程绑定吸收数据。指令应当只是用来和DOM互交。简朴来讲,假如你须要操纵DOM,那就写一个指令,然后在组件的模板中运用它。假如您须要大批的DOM操纵,还能够斟酌$ postLink性命周期钩子,然则,这不是让你将一切DOM操纵迁徙到这个函数中。

这有一些运用指令的发起:

  • 不要在运用 templates, scope, bindToController or controllers
  • 指令老是运用 restrict: 'A'
  • 必要时运用编译和链接函数
  • 记住在 $scope.$on('$destroy', fn); 中烧毁和消除绑定事宜处置惩罚顺序

Back to top

引荐的属性

因为指令支撑.component()所做的大多数事变(指令是原始组件),我发起将指令对象定义限制为仅限于这些属性,以防止毛病地运用指令:

PropertyUse it?Why
bindToControllerNoUse bindings in components
compileYesFor pre-compile DOM manipulation/events
controllerNoUse a component
controllerAsNoUse a component
link functionsYesFor pre/post DOM manipulation/events
multiElementYesSee docs
priorityYesSee docs
requireNoUse a component
restrictYesDefines directive usage, always use 'A'
scopeNoUse a component
templateNoUse a component
templateNamespaceYes (if you must)See docs
templateUrlNoUse a component
transcludeNoUse a component

Back to top

Constants or Classes

在ES2015中有几种体式格局运用指令,要么经由过程箭头函数,要么运用 Class,挑选你以为适宜的。

下面是一个运用箭头函数的例子:

/* ----- todo/todo-autofocus.directive.js ----- */
import angular from 'angular';

export const TodoAutoFocus = ($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 ----- */
import angular from 'angular';
import { TodoComponent } from './todo.component';
import { TodoAutofocus } from './todo-autofocus.directive';
import './todo.scss';

export const TodoModule = angular
  .module('todo', [])
  .component('todo', TodoComponent)
  .directive('todoAutofocus', TodoAutoFocus)
  .name;

或许运用 ES2015 Class 建立一个对象(注重在注册指令时手动挪用 “new TodoAutoFocus”)

/* ----- todo/todo-autofocus.directive.js ----- */
import angular from 'angular';

export class TodoAutoFocus {
  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 ----- */
import angular from 'angular';
import { TodoComponent } from './todo.component';
import { TodoAutofocus } from './todo-autofocus.directive';
import './todo.scss';

export const TodoModule = angular
  .module('todo', [])
  .component('todo', TodoComponent)
  .directive('todoAutofocus', ($timeout) => new TodoAutoFocus($timeout))
  .name;

Back to top

效劳

效劳概述

效劳本质上是营业逻辑的容器。效劳包含其他内置或外部效劳比方$http,我们能够在运用的其他位置注入到控制器中。我们有两种运用效劳的体式格局,.service().factory()。假如要运用ES2015的 Class,我们应当运用.service(),而且运用$inject自动完成依靠注入。

Back to top

Classes for Service

这是一个运用 ES2015 Class的例子:

/* ----- todo/todo.service.js ----- */
export class TodoService {
  constructor($http) {
    'ngInject';
    this.$http = $http;
  }
  getTodos() {
    return this.$http.get('/api/todos').then(response => response.data);
  }
}

/* ----- todo/todo.module.js ----- */
import angular from 'angular';
import { TodoComponent } from './todo.component';
import { TodoService } from './todo.service';
import './todo.scss';

export const TodoModule = angular
  .module('todo', [])
  .component('todo', TodoComponent)
  .service('TodoService', TodoService)
  .name;

Back to top

款式

运用 Webpack 我们能够在*.module.js 顶用 import 导入我们的 .scss 文件,如许做能够让我们的组件在功用和款式上都是断绝的。

假如你有一些变量或全局运用的款式,比方表单输入元素,那末这些文件依然应当放在根目次scss文件夹中。 比方 SCSS / _forms.scss。这些全局款式能够像一般那样被 @importe.

Back to top

ES2015 and Tooling

ES2015
Tooling
  • 假如想支撑组件路由,那末运用 ui-router latest alpha
  • 运用 Webpack 编译你的ES2015+代码和款式
  • 运用webpack的ngtemplate-loader
  • 运用babel的babel-plugin-angularjs-annotate

Back to top

状况治理

斟酌运用redux治理你运用的状况.

Back to top

资本

Back to top

    原文作者:chenqiao
    原文地址: https://segmentfault.com/a/1190000013668493
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞