# AngularJS 風格指南 (ES2015)css
Angular應用程序中的每一個模塊都應該是組件模塊。組件模塊用來封裝邏輯、模板、路由和子組件。github
模塊的設計直接反應了咱們的文件結構, 從而保持了可維護性和可預測性。咱們最好有三個高級模塊: root、component和common。root模塊是用來啓動咱們應用和相應模板的基礎模塊,root模塊中導入component和common模塊做爲依賴模塊。component和common模塊則負責引入Low-level modules,Low-level modules一般包含可重用的組件,控制器,服務,指令,過濾器和測試.web
Back to toptypescript
root模塊中定義整個應用的根組件,根組件中一般包含路由組件,好比ui-router
中的 ui-view
redux
// 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模塊中註冊根組件,你可能注意到樣式也被引入到了根組件,咱們將在後面的章節介紹這一點.api
全部的可重用組件應該註冊在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;
全部咱們不但願用在其餘應用中的組件應該註冊在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;
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;
保持文件名簡單,而且使用小寫字母,文件名使用 ' - '分割,好比 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
文件結構是很是重要的,這描述了一個可伸縮和可預測的結構,下面是一個例子。
├── 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/
中
組件是帶有控制器的模板,組件能夠經過 bindings
定義數據或是事件的輸入和輸出,你能夠將組件視爲完整的代碼段,而不只僅是 .component()
定義的對象,讓咱們探討一些關於組件的最佳實踐和建議, 而後深刻了解如何經過有狀態的、無狀態的和路由組件的概念來構造它們。
這些是 .component()
支持的屬性
Property | Support |
---|---|
bindings | Yes, use '@' , '<' , '&' only |
controller | Yes |
controllerAs | Yes, default is $ctrl |
require | Yes (new Object syntax) |
template | Yes |
templateUrl | Yes |
transclude | Yes |
控制器應該只和組件一塊兒使用,若是你感受須要一個控制器,可能你須要的是一個管理這個特定行爲的組件。
這是一些使用 Class
定義controllers的建議:
controller: class TodoComponent {...}
這種寫法以應對將來向Angular遷移constructor
來進行依賴注入'ngInject';
語法let ctrl = this;
也是能夠接受的class{}
上$onInit
, $onChanges
, $postLink
and $onDestroy
等生命週期函數$onChanges
is called before $onInit
, see resources section for articles detailing this in more depthrequire
alongside $onInit
to reference any inherited logic$ctrl
alias for the controllerAs
syntax, therefore do not use controllerAs
anywhereAngularJS 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
讓咱們定義一個有狀態組件
<!-- * Renders child components that mutate state
一個包括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>
組件
讓咱們第一咱們所謂的無狀態組件:
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
,這意味着在提交回父組件前,父組件的數據是不受影響的,
讓咱們定義一個路由組件。
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;
指令有 template
, scope
綁定, bindToController
, link
和不少其餘特性。在基於組件的應用中應該注意他們的使用。指令不該再聲明模板和控制器,或者經過綁定接收數據。指令應該只是用來和DOM互交。簡單來講,若是你須要操做DOM,那就寫一個指令,而後在組件的模板中使用它。若是您須要大量的DOM操做,還能夠考慮$ postLink
生命週期鉤子,可是,這不是讓你將全部DOM操做遷移到這個函數中。
這有一些使用指令的建議:
restrict: 'A'
$scope.$on('$destroy', fn);
中銷燬和解除綁定事件處理程序因爲指令支持.component()
所作的大多數事情(指令是原始組件),我建議將指令對象定義限制爲僅限於這些屬性,以免錯誤地使用指令:
Property | Use it? | Why |
---|---|---|
bindToController | No | Use bindings in components |
compile | Yes | For pre-compile DOM manipulation/events |
controller | No | Use a component |
controllerAs | No | Use a component |
link functions | Yes | For pre/post DOM manipulation/events |
multiElement | Yes | See docs |
priority | Yes | See docs |
require | No | Use a component |
restrict | Yes | Defines directive usage, always use 'A' |
scope | No | Use a component |
template | No | Use a component |
templateNamespace | Yes (if you must) | See docs |
templateUrl | No | Use a component |
transclude | No | Use a component |
在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;
服務本質上是業務邏輯的容器。服務包含其餘內置或外部服務例如$http
,咱們能夠在應用的其餘位置注入到控制器中。咱們有兩種使用服務的方式,.service()
和 .factory()
。若是要使用ES2015的 Class
,咱們應該使用.service()
,而且使用$inject
自動完成依賴注入。
這是一個使用 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;
使用 Webpack 咱們能夠在*.module.js
中用 import
導入咱們的 .scss
文件,這樣作可讓咱們的組件在功能和樣式上都是隔離的。
若是你有一些變量或全局使用的樣式,好比表單輸入元素,那麼這些文件仍然應該放在根目錄scss
文件夾中。 例如 SCSS / _forms.scss
。這些全局樣式能夠像一般那樣被 @importe
.
ui-router
latest alpha ngtemplate-loader
babel-plugin-angularjs-annotate
考慮使用redux管理你應用的狀態.