Angular 2架構總覽 - 簡書
http://www.lxweimin.com/p/aeb11061b82c
Angular 2是一個幫助我們使用HTML和JavaScript構建客戶端應用的框架。這個框架包含幾個互相協作的庫,一些是核心庫,一些是可選庫。
我們通過以下幾個步驟書寫應用:
使用Angular自定義標記編寫HTML模板
編寫組建類來處理這些模板
在services中添加應用邏輯
將頂級根組件傳遞給Angular引導
Angular接管之后,在瀏覽器中顯示應用內容,并根據我們提供的指令對用戶的交互做出響應。當然不僅僅是這些。我們將在后面內容中詳細學習。首先我們總攬全局看一下:
Paste_Image.png
這個架構圖展示了8個Angular 2應用的構成塊:
Module(模塊)
Component(組件)
Template(模板)
Metadata(元數據)
Data Binding(數據綁定)
Directive(指令)
Service(服務)
Dependency Injection(依賴注入)
下面就開始學習!
模塊
Paste_Image.png
Angular應用是模塊化的。簡言之,我們使用許多模塊組裝成我們的應用。一個典型的模塊是專于一個功能的緊密結合的代碼塊。一個模塊在代碼塊中輸出一些值,一般是像類的值。模塊系統是可選的,并非是Angular必須的,但是強烈推薦使用。模塊化開發的優點可以自行體會。也許我們遇見的第一個模塊就是一個輸出組件類的模塊。組件是Angular的基本構成塊之一,我們會講很多,在下一部分我們會討論組件。現在知道組件類是我們從模塊中輸出的最常見的東西就行了。大部分應用都有一個AppComponent
。按照慣例,我們會在名為app.component.ts
(本系列教程均使用TypeScript書寫。 TypeScript是JavaScript的超集,它可以編譯成純JavaScript,支持大多ES6特性。)的文件中找到它。在這個文件中我們會看到如下export
語句。app/app.component.ts(代碼片段)
export class AppComponent { }
這個export
語句告訴TypeScript這個模塊的AppComponent
類是公開的,可以被其他模塊使用。當我們需要引用AppComponent
時,我們像下面一樣導入即可:app/main.ts(代碼片段)
import { AppComponent } from './app.component';
這個import
語句告訴系統它可以從一個位于鄰近文件的名為app.component的模塊中獲得一個AppComponent
。模塊名經常就是文件名去掉擴展名。
庫模塊
Paste_Image.png
一些模塊是其他模塊的庫。Angular本身作為一個在幾個npm包中的庫模塊集發布。它們的名字都帶有@angular
前綴。每個Angular庫包含一個barrel模塊,事實上就是幾個邏輯相關的私有模塊的一個公共輸出口。@angular/core
庫是我們獲取所需內容的主要Angular類庫。也有其他Angular類庫,例如@angular/common
,@angular/router
和@angular/http
。我們使用幾乎一樣的方法從Angular類庫引入我們需要的東西。例如,我們從@angular/core模塊中引入Angular的Component函數:
import { Component } from '@angular/core';
對比一下我們之前引入AppComponent的語法:
import { AppComponent } from './app.component';
兩者不同在于當從Angular模塊引用時,只需要模塊名即可,不需要路徑前綴。要點總結:
Angular應用由模塊組成
模塊輸出其他模塊可以引用的類、函數、值
我們喜歡將我們的應用寫成一個模塊集合,每個模塊輸出一個東西。
我們寫的第一個模塊很可能會輸出一個組件。
組件
Paste_Image.png
一個組件控制著屏幕上一塊我們稱之為視圖的區域。應用整個顯示區域、hero列表、hero編輯器……它們都是被組件控制的視圖。我們在一個類中定義組件的應用邏輯(用來支持視圖)。類和視圖通過一個屬性和方法的API進行交互。例如,一個HeroListComponent可能包含一個heroes屬性,它從一個服務獲取一個hero數組。它可能還有一個selectHero()方法,當用戶點擊hero列表中的一個hero時會設置一個selectedHero屬性。它可能類似下面的一個類:app/hero-list.component.ts
export class HeroListComponent implements OnInit { constructor(private service: HeroService) { } heroes: Hero[]; selectedHero: Hero; ngOnInit() { this.heroes = this.service.getHeroes(); } selectHero(hero: Hero) { this.selectedHero = hero; }}
當用戶使用一個應用時,Angular不斷創建、更新和銷毀組件。開發者可以在整個生命周期的任何時間通過可選的生命周期鉤子執行一些操作。
模板
我們使用模板來定義組件的視圖。模板就是以HTML的形式告訴Angular如何渲染組件。大多數情況下一個模板看起來就像常規的HTML,可能有一些陌生(多一些Angular命令)。下面是一個HeroList組件的模板:app/hero-list.component.html
<h2>Hero List</h2><p><i>Pick a hero from the list</i></p><div *ngFor="let hero of heroes" (click)="selectHero(hero)"> {{hero.name}}</div><hero-detail *ngIf="selectedHero" [hero]="selectedHero"></hero-detail>
我們認識其中的
和<div>。但是也有其他一些我們不認識的標記,例如*ngFor, {{ hero.name }}, (click), [hero]和<hero-detail>。這些是Angular模板語法的例子,我們會慢慢習慣甚至喜歡它。之后我們會詳細解釋。在解釋之前,我們先關注一下最后一行。<hero-detail>標記是一個代表HeroDetailConponent的自定義元素。HeroDetailConponent和我們之前見過的HeroListComponent不同。HeroDetailConponent展示一個具體的hero的信息,而這個hero是用戶在HeroListComponent展示的列表中選擇的一個hero。HeroDetailConponent是HeroListComponent的一個子組件。我們可以看到<hero-detail>和其他我們已知的HTML元素和諧地在一起,我們可以在布局中混合使用自定義組件和原生HTML。
Paste_Image.png
我們會用這種方式編寫復雜的組件樹以構建一個功能豐富的應用。
Angular Metadata
Metadata告訴Angular如何處理一個類。回看HeroListComponent,它就是一個類,毫無框架的痕跡,里面也沒有Angular。事實上,它就是一個類。只有我們將它通告給Angular,它才算一個組件。我們通過將metadata附屬到類來告訴Angular HeroListComponent是一個組件。用TypeScript附加metadata的簡單方法是使用一個decorator。下面是HeroListComponent的一些metadata:app/hero-list.component.ts
@Component({ selector: 'hero-list', templateUrl: 'app/hero-list.component.html', directives: [HeroDetailComponent], providers: [HeroService]})export class HeroesComponent { ... }
在以上代碼中我們看到@Component decorator將它下面的HeroesComponent類標記成一個組件類。一個decorator是一個函數,它經常有一個配置參數。@Component decorator接受一個配置對象,其中包含著Angular用來創建和顯示組件及其視圖的信息。下面是一些可能的@Component配置選項:
selector 插入組件的標簽名
templateUrl 組件模板地址
directives 一個當前模板需要的組件和指令的數組。
providers 一個組件需要的服務(依賴注入)的數組。
Paste_Image.png
@Component函數接受配置對象并將其轉換為metadata,然后附加到組件類定義中。Angular在運行時發現這些metadata并因此指導如何做“正確的事”。模板、metadata和組件一起描述視圖。我們使用類似的方法應用其他metadata decorator來指導Angular行為。隨著我們應用功能的增強,@injectable, @Input, @Output, @RouterConfig是幾個我們需要掌握的decorator。
重點:我們必須在代碼中添加metadata以讓Angular知道做什么。
數據綁定
Angular提供數據綁定,一種協調配合模板部分和組件部分的機制。我們在模板中添加綁定標記以告訴Angular如何連接兩部分。有四種數據綁定的語法。每種都有一個方向:到DOM、從DOM、雙向,可以通過下圖的箭頭方向區別。
Paste_Image.png
我們在下面的例子綁定中可以看到三種數據綁定:app/hero-list.component.html
<div>{{hero.name}}</div><hero-detail [hero]="selectedHero"></hero-detail><div (click)="selectHero(hero)"></div>
插值 將組件的hero.name屬性值顯示到<div>標簽中
屬性綁定 將父組件的selectedHero屬性傳遞給子組件的hero屬性中
事件綁定 當用戶點擊hero的名字時調用組件的selectHero方法
雙向數據綁定是第四種形式,很重要。它使用ngModel指令在一個簡單表達中將屬性和事件綁定組合到一起。我們在HeroListComponent模板中沒有雙向數據綁定,下面是HeroDetailComponent模板中的例子。
<input [(ngModel)]="hero.name">
在雙向數據綁定中,一個數據屬性值通過屬性綁定從組件流入到輸入框中。用戶對數據的改變通過數據綁定又流回到組件中,將屬性設為最新值。Angular在每個JavaScript事件循環中處理一次所有的數據綁定,從應用組件樹的根節點開始深度優先遍歷。
Paste_Image.png
Paste_Image.png
我們并不知道所有的細節,但是數據綁定在模板和組件之間、父組件和子組件之間通信中的重要作用大家有目共睹。
指令
我們的Angular模板是動態的。當Angular渲染它們時,它根據指令轉變DOM。指令是包含指令元數據的一個類。在TypeScript中我們使用@Directive decorator將元數據添加到類上。我們已經見過一種指令——組件。一個組件就是包含模板的一個指令,@Component decorator事實上是一個使用面向模板特性擴展的@Directive decorator。還有兩種指令,我們稱之為結構指令和屬性指令。它們傾向于在元素標簽內部出現,有時以名字的形式,但更多是作為一個賦值或綁定的目標的形式。結構化指令通過添加、刪除和替換DOM來改變布局。我們在我們的案例模板中見過兩個內置結構化指令:
<div *ngFor="let hero of heroes"></div><hero-detail *ngIf="selectedHero"></hero-detail>
*ngFor告訴Angular對heroes列表中的每一個hero構建一個<div>
*ngIf只在有hero被選中時才包含HeroDetail組件屬性指令改變已有元素的表現和行為。在模板中它們看起來就像普通的HTML屬性。ngModel指令就是一個屬性指令,它實現了雙向數據綁定。<input [(ngModel)]="hero.name">
它通過設置元素的顯示值屬性和響應change事件來改變已有元素的行為。Angular自帶一些改變布局結構(例如ngSwitch)或者改變DOM元素和組件方面(例如ngStyle和ngClass)的指令。當然我們也可以編寫自己的指令。
服務
服務是一個很寬泛的范疇,可以包含我們應用需要的任何值、函數和特性。基本上任何東西都可以是一個服務。一個典型的服務就是一個特定的、設計良好的類。它應該做一些具體的事情,并且做的很好。例子包含:
logging服務
數據服務
message bus
稅計算器
應用配置
關于服務Angular沒有具體的說明。Angular本身并沒有定義服務。并沒有一個ServiceBase類。然而服務對于Angular應用是基本的。下面是一個記錄日志到瀏覽器console的服務類的例子:app/logger.service.ts(只有類)
export class Logger { log(msg: any) { console.log(msg); } error(msg: any) { console.error(msg); } warn(msg: any) { console.warn(msg); }}
下面是一個HeroService,它獲取heroes并以一個promise的形式返回它。HeroService依賴LoggerService和一個簡單用于和服務器通信的BackendService。app/hero.service.ts(只有類)
export class HeroService { constructor( private backend: BackendService, private logger: Logger) { } private heroes: Hero[] = []; getHeroes() { this.backend.getAll(Hero).then( (heroes: Hero[]) => { this.logger.log(Fetched ${heroes.length} heroes.
); this.heroes.push(...heroes); // fill cache }); return this.heroes; }}
服務遍及各處。我們的組件就是最大的服務消費者。他們依賴服務來處理大多數事物。它們不從服務器獲取數據,不驗證用戶輸入,不直接在console中記錄。他們將這些任務都委托給服務。組件的工作就是增強用戶體驗,無它。它協調視圖(由模板渲染得到)和應用邏輯(經常包含model)。一個好的組件展示用于數據綁定的屬性和方法,將其他無關緊要的事委托給服務。Angular并不強制這些原則。如果你要寫一個3000行的“廚房水槽”組件它也不會抱怨。但Angular讓我們易于將應用邏輯包含到服務中,也通過依賴注入讓服務易于被使用。
依賴注入
依賴注入是一個為一個類的新實例提供它需要的全部依賴的方法。大部分依賴都是服務。Angular使用依賴注入來向新組件提供它們需要的服務。在TypeScript中,Angular可以通過查看構造器參數來確認要為一個組件提供哪些服務。例如,我們的HeroListComponent組建需要HeroService:app/hero-list.component(構造器)
constructor(private service: HeroService) { }
當Angular創建一個組件時,它首先向Injector請求組件需要的服務。一個Injector維護一個它之前創建的服務實例的容器。如果請求的服務實例在容器中不存在,Injector就創建一個并把它添加到容器中,然后將這個服務返給Angular。當所有請求的服務都被解析并返回,Angular就會調用組件的構造器,并將那些服務作為參數。這就是依賴注入的含義。HeroService注入的過程類似下圖:
如果Injector沒有HeroService實例,它怎么知道如何創建一個呢?簡而言之,我們必須事先到Injector注冊一個HeroService的provider。一個provider就是一個可以創建并返回一個服務的東西,一般就是服務類自己。我們可以在應用組件樹的任一級注冊provider。我們經常在引導應用時在根極元素注冊以讓同一服務的實例可以隨處可用。app/main.ts(代碼片段)
bootstrap(AppComponent, [BackendService, HeroService, Logger]);
當然也可以在組件級注冊app/hero-list.component.ts(代碼片段)
@Component({ providers: [HeroService]})export class HeroesComponent { ... }
在這個案例中我們在組件的每一個新實例中都獲得服務的一個新實例。在這個概覽中我們已經大大簡化了依賴注入。我們可以在依賴注入章節看到詳細說明。要點是:
依賴注入被綁定到框架中,到處都在用
Injector是主要機制一個Injector維護一個它創建的服務的容器
Injector可以使用provider創建新服務
一個Provider是創建一個服務的菜譜(說明)
我們向injector注冊provider。
總結
我們已經學習了Angular應用程序的8個主要構成部分。這只是一個基礎,我們還有很多要學。
其他
下面是一個簡單的,按字母順序排列的Angular其他重要特性和服務。它們中的大部分都在開發指南中涉及。
Animation動畫——一個現成的動畫庫,它讓開發者不知道太多動畫技巧和CSS也能為組件添加動畫效果。Bootstrap引導——一個配置和啟動根應用組件的方法。Change Detection變化檢測——學習Angular如何判定一個組件的屬性值發生了改變并更新屏幕。學習它如何使用zones來攔截異步活動并運行它的變化檢測策略。Component Router組件路由——使用組件路由服務,用戶可以使用熟悉的URL方式在一個多視圖應用中導航。Events(事件)——DOM可以發起事件,組件和服務也可以。Angular提供了發布和注冊事件的機制,其中包含一個RxJS Observable proposal的實現。Forms表單——基于HTML數據驗證和臟檢查,支持復雜的數據輸入場景。HTTP——使用這個Angular HTTP客戶端和服務器通信以獲取數據,保存數據,觸發服務端action。Lifecycle Hooks生命周期鉤子——我們可以通過實現鉤子接口,利用組件生命周期從創建到銷毀的每一個關鍵時刻。Pipes管道——改變顯示值的服務(其實就是過濾器啦!)。我們可以將管道放入模板來改善用戶體驗。例如,下面的currency管道表達式:
price | currency:'USD':true
這個例子將價格42.33顯示為$42.33。Testing測試——Angular提供了一個測試庫用于在和Angular框架進行交互時對我們的應用部分進行單元測試。
作者:JasonQiao鏈接:http://www.lxweimin.com/p/aeb11061b82c來源:簡書著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。