半小時入門Angular 2

本文首發於由電子工業出版社出版《揭祕Angular 2》一書,基於第5章概覽改寫。
做者簡介:廣發證券互聯網金融技術團隊,是Angular早期堅決的踐行者。做爲全新一代的證券業 IT 研發組織,團隊致力於用更新更好的技術打造行業方案、支持業務創新。
責編:陳秋歌,尋求報道或者投稿請發郵件至chenqg#csdn.net,或加微信:Rachel_qg。
瞭解更多前沿技術資訊,獲取深度技術文章推薦,請關注CSDN研發頻道微博css

Angular 2.0 於去年 9 月正式發佈。html

儘管同一時間裏 React/Vue 大放光彩,圈子社區也是熱鬧非凡,他們的優勢不言而喻,不過這裏不談 React / Vue,更不是要比出個誰優誰劣(知乎上的對比已經不少了[Facepalm]),這篇文章只關注 Angular 自己,好好地聊聊 Angular 這個遲來的平臺。前端

新版 Angular 推出後,官方再也不取用 AngularJS 這個名稱了,而是推薦直接使用 Angular,AngularJS 變爲專指第一代框架,因此後文裏採用 Angular 均指代 Angular 2+。python

咱們知道,Angular 完全重寫了 AngularJS,這多少會給社區帶來了不便,好在 Angular 團隊在無縫升級方面下了很多功夫,並且更重要的是重寫能讓 Angular 拋掉老版的包袱。採用新架構設計的 Angular 代碼更簡潔易讀、性能更高,更加貼合新時代前端的發展趨勢,如基於組件的設計、響應式編程等。除此以外,Angular 適用場景更廣,如支持服務端渲染,能更好地適配 Mobile 應用(Mobile Toolkit)、支持離線編譯等。談及 Angular,他實際上包含了一整套解決方案,而這篇文章重點放在 Angular 自身的核心概念介紹,目的是幫助你們入門 Angular 平臺,因此文章的深度與廣度都通過反覆推敲,適合 Angular 初學者。git

核心概念

Angular 官方文檔列出了 8 個核心概念,分別是模塊、組件、模板、元數據、數據綁定、指令、服務、依賴注入。若是你熟悉 React/Vue 開發,部分概念實際上是暗合相通的。若是把這些概念串聯起來,從總覽的角度看各個概念在應用中所處的位置,大概是這樣子:github

圖片描述

藉助上圖,咱們來大體解讀一下這些核心概念:express

  • 與用戶直接交互的是模板,模板並非獨立的模塊,它是組成組件的要素之一。另外一要素是組件類,用以維護組件的數據模型及功能邏輯;
  • 模板是經過元數據指定的,元數據還包含不少其餘的重要信息,這些信息是用來告訴 Angular 如何去解釋一個普通的類,如上圖所示,元數據結合普通類而構成組件;
  • 指令是 Angular 裏的獨立構成,他與模板密切關聯,用來加強模板特性,間接擴展了模板的語法;
  • 服務也是 Angular 裏的獨立構成,他是封裝單一功能邏輯的單元,一般爲組件提供功能擴展;
  • 服務要能爲組件所使用,是經過「依賴注入」機制把服務引入到組件內部,服務既能夠單獨注入到某一組件,亦可注入到模塊,兩種注入方式使得服務的做用域不同,後文詳解。

Angular 的概念雖多,但理解起來並不難,這些概念中最重要的就是組件。 縱觀整個 Angular 應用,接收用戶指令,加工處理後輸出相應視圖的過程當中,組件始終處於這個交互的出入口,這正是 Angular 基於組件設計的體現。下面以組件爲切入點,逐一揭開這些核心概念的面紗。編程

組件bootstrap

Angular 框架基於組件設計,其應用由一系列大大小小的組件構成。 Angular 組件的含義實際上跟 React/Vue 的組件是相似的,下面沿用《揭祕Angular 2》書籍裏的通信錄例子(源碼地址)來講明,其 Demo 的效果圖以下所示:設計模式

圖片描述

全部框起來的部分均是由相應的組件所渲染,而且這些組件層層嵌套,自上而下構成組件樹。如最外層的方框爲根組件,包含了 Header、ContactList 以及 Footer 三個子組件,其中 ContactList 又有本身的子組件。

圖片描述

先來聚焦單個組件,每一個 Angular 組件內部除了有獨立的 JavaScript 邏輯,還包含有 HTML(即模板)及 CSS 代碼。因此每一個組件不只有本身獨立的業務邏輯,也有屬於本身的視圖層來渲染本身。

舉一個簡單的 Contact 組件示例代碼:

import { Component } from '@angular/core'; @Component({ selector: 'contact', template: '<p>張三</p>' }) class ContactComponent { constructor() { } }

能夠看出 Angular 組件由兩部分組成,@Component() 和 TS/ES6 的類。TS/ES6 類是處理組件的業務邏輯,而 @Component() 這部分稱爲裝飾器,裝飾器是 TypeScript 提供的一種語言特性,用來往類、函數等注入額外的信息,這些額外的信息實際上就是 Angular 核心概念——元數據。在 Angular 裏,元數據主要以裝飾器的函數參數指定。上例中定義了兩個重要元數據 selector 和 template,template 顧名思義即爲模板,selector 聲明的是一個 CSS3 選擇器,應用運行時匹配模板上的DOM元素,簡單理解其實就是組件的標籤名。

裝飾器其實是一個自定義函數,Angular 的各類裝飾器處理邏輯在 Angular 源碼:modules/@angular/core/src/util/decorators.ts。

關係示意圖以下所示:

圖片描述

引入CSS樣式的元數據爲 styles,更多組件元數據可點擊這裏查看。

若是咱們僅僅定義了一個類,Angular 並不知道該如何解釋這個類。當往這個類裏注入組件元數據後,Angular 才知道把這個類解釋爲組件。相似的還有指令元數據,把普通類解釋爲一個指令。

若是想了解元數據是如何注入到類裏,可深刻了解 reflect-metadata 這個 polyfill。

雖然每一個組件各司其職,但組件以樹的形式來組織意味着,組件不多是孤立的存在,父子組件之間存在着雙向的數據流動。每一個組件都可以定義本身的輸入輸出屬性,這些屬性成爲了組件的對外接口,負責跟父組件進行交互。

咱們來完善 Contact 組件,添加輸入輸出屬性,僞代碼以下:

// import Component, Input, Output, EventEmitter, etc @Component({ selector: 'contact', template: '<p>張三</p>' }) export class ContactComponent { @Input() item: ContactModel; // 輸入屬性 @Output() update: EventEmitter<ContactModel>; // 輸出屬性 constructor() { } modify() { // ... this.update.emit(newValue); } }

@Input() 和 @Output() 也是裝飾器,裝飾的目標爲類的成員屬性,而 @Component() 裝飾的目標是類。

@Input() 和 @Output 聲明瞭組件 Contact 的輸入輸出接口,item 變量用來接收來自父組件的數據源輸入,update 事件用於向父組件發送數據。輸入輸出屬性分開,這跟 React 的 props 屬性略有不一樣。

定義好 Contact 組件輸入輸出接口後,接下來就與父組件 ContactList 交互。ContactList 父組件的示例代碼以下:

// import statement
@Component({
  selector: 'contact-list',
  template: `
    <!-- 使用 <contact> 標籤調用 Contact 組件 --> <contact [item]="items[0]" (update)="doUpdate(newValue)"></contact> ` }) export class ContactListComponent { items: ContactModel[] constructor() {} doUpdate(item: ContactModel) { // ... } }

父組件 ContactList 的模板裏要能直接使用子組件 Contact 定義的標籤,須要有一個導入的過程,須要依賴「模塊」的特性,後文展開。

ContactList 組件的模板調用了 Contact 組件,其中 [item] 稱爲屬性綁定,數據從父組件流向子組件;(update) 稱爲事件綁定,數據從子組件流向父組件。

圖片描述

屬性綁定的 [] 和事件綁定 () 不能省略,這是語法的重要組成部分。

屬性綁定和數據綁定均稱爲數據綁定,這個 Angular 強調的核心概念之一。細心的讀者可能已經發現,屬性綁定和數據綁定是能夠直接引用組件的成員屬性,如 listItem 和 doUpdate()。屬性綁定和事件綁定既用於組件數據模型和模板視圖之間的數據傳遞,也同時用於父子組件的數據傳遞,在父子組件通訊的過程當中,模板充當相似於橋樑的角色,鏈接着兩者的功能邏輯。

圖片描述

這種通信方式適用於層級相隔不遠的組件,層級太深或者不一樣分支的組件通信一般採用其餘方式,例如利用服務做爲中介。

這就是 Angular 的數據流動機制,然而流動並非自發造成,流動須要一個驅動力,這個驅動力便是 Angular 的變化監測機制。Angular 是一個響應式系統,每次數據變更都幾乎能實時處理,並更新對應視圖。那麼 Angular 是如何感知數據對象發生變更呢?ES5 提供了 getter/setter 語言接口來捕捉對象變更,這是 VueJS 採用的方式,然而 Angular 並無採用之。Angular 是以適當的時機去檢驗對象的值是否被改動,這個適當的時機並非以固定某個頻率去執行,而一般是在用戶操做事件(如點擊),setTimeout 或 XHR 回調等這些異步事件觸發以後。Angular 捕獲這些異步事件的工做是經過 Zones 庫實現的,變化監測事件圖以下所示:

圖片描述

從上圖能夠看出,每一個組件背後都維護着一個獨立的變化監測器,這個變化監測器記錄着所屬組件的數據變動狀態。因爲應用是以組件樹的形式組織,所以每一個應用也有着對應的一棵變化監測樹。當 Zones 捕獲到某異步事件後,一般它都會通知 Angular 執行變化監測操做,每次變化監測操做都始於根組件,並以深度優先的原則向葉子組件遍歷執行,並且每一個組件的變化監測器都對其組件的數據模型通過優化,檢測的性能很是高。

智能化的變化監測機制使得開發者沒必要關心數據什麼時候何地被變更,他老是能找到適當的時機去觸發數據檢測,這就是 Angular 強大的數據變化監測機制。而當檢測到數據發生變更時,結合數據綁定從而驅動模板視圖的實時更新,這就是咱們所看到的實時更新的效果。

那麼咱們繼續延伸,假若在發現輸入數據有變更的時機裏,咱們須要去作一些額外的處理,怎麼辦?Angular 提供了完善的生命週期鉤子來解決這個問題,如 ngOnChanges 能夠知足剛提到的捕獲輸入數據變更時機的要求,使用方法也很簡單,直接定義一個同名實例方法便可:

export class ContactComponent {
  @Input() item: ContactModel; // 輸入屬性
  @Output() update: EventEmitter<ContactModel>; // 輸出屬性
  constructor() { }
  modify() {
    // ... this.update.emit(newValue); } ngOnChanges(changes: SimpleChanges) { // 變化檢測鉤子,item值變更時觸發 // changes 包含了變更先後狀態 // ... } }

經常使用的生命週期鉤子有:

圖片描述

  1. 最早觸發的是構造函數,你能夠作些組件類的初始化工做,例如類變量初始賦值等。
  2. 接下來會觸發 ngOnChanges 鉤子,這是 ngOnChanges 鉤子的第一次觸發,主要用來接收來自父組件傳入的數據,爲接下來的組件的初始化工做提供數據支持。
  3. 而後就到了 ngOnInit 鉤子,這個纔是實際意義的組件初始化階段,Angular 不推薦在構造器初始化階段處理一些業務邏輯相關的工做,更好的方式是放在 ngOnInit 階段來處理。
  4. 接下來,組件就進入穩按期,這個時期 ngOnChanges 鉤子能夠反覆觸發。只要從輸入屬性獲取到的數據的發生變化,ngOnChanges 鉤子就會觸發一次。
  5. 最後,在組件銷燬以前會觸發 ngOnDestroy 鉤子,在這個階段能夠用來作一些清理工做,如事件解綁,取消數據訂閱等等。

以上即是組件的簡述,同時簡單介紹了元數據數據綁定,做爲組件的重要要素模板並無展開,Angular 爲模板提供了強大的功能特性,咱們繼續。

模板

Angular 模板基於 HTML,普通的 HTML 亦可做爲模板輸入:

@Component({ template: `<p>張三</p>` })

但 Angular 模板不止於此,Angular 爲模板定製出一套強大的語法體系,涉及內容頗多,這也是爲何將模板單獨列出的緣由。數據綁定是模板最基本的功能,除了前述提到的屬性綁定和事件綁定,插值也是很常見的數據綁定語法,示例代碼以下:

// import statement
@Component({
  selector: 'contact', template: '<p>{{ item.name }}</p>' }) export class ContactComponent { @Input() item: ContactModel; // ... }

插值語法是由一對雙大括號 {{}} 組成,插值的變量上下文是組件類自己,如上例中的 item,插值是一種單向的數據流動 —— 從數據模型到模板視圖。

上面提到的三種數據綁定(即屬性綁定、事件綁定以及插值)語法的數據流動都是單向的,在某些場景下須要雙向的數據流動支持(如表單)。結合屬性綁定和事件綁定,Angular 模板可實現雙向綁定的功能,如:

<input [(ngModel)]="contact.name"></input>

[()] 是實現雙向綁定的語法糖,ngModel 是輔助實現雙向綁定的內置指令。上述代碼執行後,Input 控件和 contact.name 之間就造成雙向的數據關聯,Input 的值發生變動時,可自動賦值至 contact.name,而 contact.name 的值被組件類改變時,亦可實時更新 Input 的值。

由上可知,數據綁定負責數據的傳遞與展現,而針對數據的格式化顯示,Angular 提供了一種叫管道的功能,使用豎線 | 來表示,示例代碼以下:

<span>{{ contact.telephone | phone }}</span>

假設上述 contact.telephone 的值是 18612345678,這一串數字並不太直觀,管道命令 phone 能夠將其進行美化輸出,如 「186-1234-5678」,而不影響 contact.name 自己的值。管道支持開發者定製開發,phone 即屬於自定義管道。Angular 也提供了一些基本的內置管道命令,如格式化數字的 number、格式化日期的 date 等。

上述是 Angular 模板主要的語法特性,這篇綜述文意在幫助入門理解,不會面面俱到。除了基本的語法特性,模板還有一套強大的 「指令」 機制,來簡化一些特定的交互場景,如樣式處理、數據遍歷以及表單處理等。

指令

指令與模板關係密切,指令能夠與 DOM 進行靈活交互,它或是改變樣式,或是改變佈局。瞭解過 AngularJS 的開發者可能會有疑問,這裏的指令跟 AngularJS 的指令是一回事麼? 雖然 Angular 指令跟 AngularJS 指令在功能有相似之處,但兩者並不徹底是同一個概念。Angular 指令的範疇很廣,實際上組件也是指令的一種。組件與通常指令的區別在於:組件帶有單獨的模板,即 DOM 元素,而通常的指令是做用在已有的 DOM 元素上。通常的指令分爲兩種:結構指令和屬性指令。

圖片描述

結構指令可以添加、修改或刪除 DOM,從而改變佈局,如 ngIf:

<button *ngIf="canEdit"> 編輯 </button>

當 canEdit 的值爲 true 時,button 按鈕會顯示到視圖上;若 canEdit 爲 false 時,button 按鈕會從 DOM 樹上移除。

注意結構指令的 * 號不能丟掉,這是 Angular 爲了使用簡便實現的語法糖。

屬性指令用來改變元素的外觀或行爲,使用起來跟普通的 HTML 元素屬性很是類似,如 ngStyle 指令,用於動態計算樣式值,示例代碼以下:

<span [ngStyle]="setStyles()">{{ contact.name }}</span>

標籤的樣式由 setStyles() 函數計算得出,setStyles() 是其組件類的成員函數,返回一個計算好的樣式對象,示例代碼以下:

class ContactComponent { private isImportant: boolean; setStyles() { return { 'font-size': '14px', 'font-weight': this.isImportant ? 'bold' : 'normal' } } }

上面列舉的 ngIf 和 ngStyle 都是 Angular 的內置指令,相似的還有 ngFor、ngClass 等,這些內置指令爲模板提供了強大語法支持。指令更具吸引力的地方在於支持開發者自定義,自定義指令能最大限度地實現 UI 層面的邏輯複用。

ngIf 和 ngStyle 等這些內置指令要能在組件模板裏直接使用,須要有一個聲明導入的過程,這個過程是藉助模塊的特性,後文展開。

服務

服務是封裝單一功能的單元,相似於工具庫,常被引用於組件內部,做爲組件的功能擴展。那服務包含什麼?它能夠是一個簡單的字符串或是 JSON 數據,也能夠是一個函數甚至是一個類,幾乎全部的對象均可以封裝成服務。以日誌服務爲例,一個簡單的日誌服務以下所示:

// import statement @Injectable() export class LoggerService { private level: string; setLevel(level: string) { this.level = level; } debug(msg: string) { } warn(msg: string) { } error(msg: string) { } }

@Injectable() 是服務類裝飾器。

這個服務的功能很簡單,只專一於日誌功能,Angular 應用裏每一個組件均可以複用到這個日誌服務給本身新增日誌記錄的能力,而不須要每一個組件重複實現,這就是設計服務的主要原則。那麼服務怎麼樣爲組件所使用?這就須要引入依賴注入機制。

依賴注入

在服務小節裏會提到過「注入」這個概念,依賴注入一直都是 Angular 的賣點。經過依賴注入機制,服務等模塊能夠被引入到任何一個組件(或模塊,或其餘服務)中,而開發者無須關心這些模塊是如何被初始化。由於 Angular 已經幫你處理好,包括該模塊自己依賴的其餘模塊也會被初始化。以下圖所示,當組件注入日誌服務後,日誌服務以及它所依賴的基礎服務都會被初始化。

圖片描述

能夠說,依賴注入是一種幫助開發者管理模塊依賴的設計模式。在 Angular 中,依賴注入與 TypeScript 相結合提供了更好的開發體驗。在 TypeScript 中,對象一般被明確賦以類型,經過類型匹配,組件類即可知道該用哪一種類型實例去賦值變量。一個簡單的依賴注入例子以下所示:

import {LoggerService} from './logger-service'; // other import statement @Component({ selector: 'contact', template: '...' providers: [LoggerService] }) export class ContactListComponent { constructor(logger: LoggerService) { logger.debug('xxx'); } }

@Component 裝飾器中的 providers 元數據是依賴注入操做的關鍵,它會爲該組件建立一個注入器對象,並新建 LoggerService 實例存儲到這個注入器裏。組件須要引入 LoggerService 實例時,只需在構造函數聲明 LoggerService 類型的參數便可,Angular 自動地經過類型匹配,找出注入器裏預先實例化好的 LoggerService 對象,在組件實例化化時做爲參數傳入,這樣組件便得到了 LoggerService 的實例引用。

值得注意的是,組件上建立的這個注入器對象是能夠被子組件複用的,這就意味着咱們只需在根組件上注入一次服務,即在根組件的 providers 聲明注入該服務,整棵組件樹上的組件都能使用這個服務,而且保持單例。這個特性很是有用,大大節省了服務的內存佔用,而且因爲服務是單例的,注入到組件後,能夠做爲中轉橋樑,實現這些組件之間的數據傳遞。

這時候,你們可能會有個疑問,在某個組件分支裏,我不想繼續沿用這個實例了,我但願使用一個新實例,這種場景其實並很多見,那麼這種狀況 Angular 怎麼解決?答案是分層注入。組件樹上的每一個組件都能單獨注入服務,服務的每一次注入(也就是使用 providers 聲明),該服務都會被建立出新的實例,該組件及其全部子組件都會轉而使用這個新的實例。舉個例子:

我在根組件注入了 LoggerService 日誌服務,並設置日誌級別 level 爲 warn 級別。

// import statement // 根組件 @Component({ selector: 'app', template: '...', providers: [LoggerService] }) class AppComponent { constructor(logger: LoggerService) { logger.setLevel('warn'); } }

那麼組件樹上的全部組件都能使用到這個 warn 級別的日誌服務:

// ContactList 組件 @Component({ selector: 'contact-list', template: '...' }) class ContactListComponent { constructor(logger: LoggerService) { // 在構造器裏聲明便可 } }

接下來,隨着業務發展,我但願在 ContactList 組件分支上能輸出更高級別(如 debug 級別)的日誌信息,很顯然,經過在 ContactList 組件裏修改 level 會有一些反作用:

class ContactListComponent { constructor(logger: LoggerService) { logger.setLevel('debug'); // 這個 logger 是全局實例 } }

他獲取到的 logger 是根組件注入的實例,在任何一個子組件調用 setLevel() 都是全局生效的,使得根組件也輸出了 debug 級別的信息。這時候咱們只須要在 ContactList 組件裏從新注入 LoggerService 實例,便可知足需求:

// ContactList 組件 @Component({ selector: 'contact-list', template: '...', providers: [LoggerService] // 從新注入 }) class ContactListComponent { constructor(logger: LoggerService) { logger.setLevel('debug'); } }

ContactList 分支使用的是新的 debug 級別的日誌服務,而根組件和 Header 等其餘組件依然能繼續使用 warn 級別的日誌服務。

圖片描述

組件以樹的形式組織,使得組件背後的注入器對象也能夠抽象爲一顆樹,稱爲注入樹。Angular 首先會從宿主組件對應的注入器查找匹配的服務實例,若找不到,則繼續往父組件的注入器裏查找,一直找到最頂層的注入器爲止,若都找不到匹配的實例,則拋出錯誤。這種靈活的注入方法能夠適應多變的應用情景,既可配置全局單例服務(在應用的根組件注入便可),亦可按需注入不一樣層級的服務,彼此數據狀態不會相互影響。

前面提到過,依賴注入除了能夠做用於組件,也能夠做用於模塊,要理解模塊的依賴注入,首先理解模塊是什麼,咱們繼續。

模塊

首先說明的一點,模塊有兩層含義:

  1. 框架代碼以模塊形式組織(物理模塊)
  2. 功能單元以模塊形式組織(邏輯模塊)

物理模塊是 TS/ES6 提供的文件模塊特性,並非本文重點,這裏重點剖析的是邏輯模塊,下面邏輯模塊直接稱爲模塊。

一個大型應用由大量組件、指令、管道、服務構成,這些構件中有些是沒有交集的,而有些則協同工做來完成某個特定的功能,咱們但願把這些有關聯的構件包裝到一塊,造成一個比較獨立的單元,這樣的單元在實際意義上就稱爲模塊。 因此簡單的說,模塊就是對應用內零散的組件、指令、服務按功能進行歸類包裝。其關係示意圖以下:

圖片描述

除此以外,模塊還有一個重要的實際意義。由於默認狀況下,一個組件是不能直接引用其餘組件,也不能直接使用其餘指令的功能,要想使用須要先導入,其餘前面講父子組件時候已經提到過,這個導入的過程就是應用模塊實現的。 總結來講,一個組件能夠任意使用同模塊的其餘組件和指令。 可是,跨模塊裏的組件指令則不能直接相互使用,如模塊A的組件不能直接使用模塊C的指令,若要跨模塊訪問,則需結合模塊的導入導出功能,要理解導入導出的內容,先來看一個簡單的模塊例子:

// import statement @NgModule({ imports: [SomeModule], // 導入其餘模塊 declarations: [SomeComponent, SomeDirective, SomePipe], // 引入組件、指令、管道 providers: [LoggerService], // 依賴注入 exports: [SomeComponent, SomeDirective, SomePipe] // 導出組件、指令、管道 // bootstrap: [AppComponent] // 根模塊纔有,標記哪一個組件是根組件 }) export class AppModule { }

能夠看出,聲明模塊使用的是 @NgModule() 裝飾器。先來看 imports 和 exports 屬性,他們即爲模塊的導入導出屬性,模塊間的導入導出關係以下圖所示:

圖片描述

由圖可知,模塊A 導入了 模塊B,模塊B 經過 exports 屬性暴露了 組件B1 和 指令B2。 很顯然,組件B1 和 指令B2 可以被 組件A1 使用,而 組件B3 並不能。 因此能夠看出,Angular 模塊既能夠對外暴露出一些構件,同時又有必定的封裝性,可以隱藏內部的一些實現。

講完了模塊內的組件和指令(管道的訪問方式跟組件指令一致),接下咱們來看一下服務,接上文依賴注入拋過來的水球。服務既可注入到組件也可注入到模塊,兩者的使用方法大體相同,區別在於做用域。全部的模塊上都共享着一個應用級別的注入器,這就意味着注入到任何一個模塊的服務能夠在應用全局(全部模塊)裏使用,而注入到組件裏的,僅能在該組件以及它的子組件上使用。

關於應用級注入器和組件級注入器的關係以下所示:

圖片描述

應用級注入器的子節點除了有組件級注入器,還包含懶加載模塊級注入器,懶加載的模塊的注入器是獨立生成的,爲模塊級別注入器,這個不在本文講解範圍內。

能夠看出,組件級注入器是全局注入器的一個子注入器,因此回看上面這個例子,模塊B 裏的 服務B4 既能夠在 模塊A 裏使用,也能夠 模塊C 裏使用。

這裏你們可能會有疑問,若是不一樣的模塊裏都注入了相同標識的服務,因爲模塊都共享同一個注入器,免不了會發生衝突。只要記住一個原則便可,後初始化的服務會覆蓋先初始化的服務,舉個例子,模塊A 和 模塊C 都注入了 LoggerService,而且模塊A 導入了 模塊C,因爲 模塊C 會先初始化,而後纔到 模塊A,因此 模塊A 注入的 LoggerService 會被應用到全局。特別特別提醒的一點是,即便 模塊C 也注入了 LoggerService,該模塊裏生效的實例也會是 模塊A 裏注入的那個實例,必定要記住這點。按照這個理論來推導,根模塊裏注入的服務始終是最高優先級的。

上述主要介紹了模塊的特性,接下來看一下 Angular 給咱們推薦的模塊使用的最佳實踐。

圖片描述

首先,Angular 要能成功運行,至少須要定義一個模塊,由於須要有一個模塊做爲應用啓動的入口,這樣的模塊就稱爲根模塊。

而後,咱們的應用會不斷的添加新的功能。這些新增的功能能夠封裝到一個新的模塊裏。這些新增長的模塊在 angular 裏稱爲特性模塊。有了特性模塊以後,根模塊原來承載在功能邏輯也能夠抽離出來,放到某個特性模塊裏,使根模塊保持簡潔。

接下來,咱們添加的特性模塊愈來愈多,他們之間能夠抽出一些類似功能的組件或指令,這些公共的部分也能夠封裝成一個獨立的模塊,這樣的模塊在邏輯意義上不能稱爲特性模塊,Angular 把他稱爲爲共享模塊。

最後,還有核心模塊,咱們知道,一個應用裏總有一些全局的組件或服務等,他們只須要在應用啓動時候初始化一次便可,例如,維護登陸信息的服務,或者是,公共的頭部和尾部組件等。雖然咱們能夠把他們放到根模塊裏,但更好的設計是把這些邏輯也抽離出來,放到一個獨立的模塊,這個模塊即爲核心模塊。核心模塊要求只導入到根模塊裏,而儘可能不要導入到特性模塊或者共享模塊裏,這是爲了在協同工做時候避免出現一些不可預料的結果。

這就是 Angular 給咱們推薦最佳實踐。最終咱們看到,處於總指揮地位的根模塊很是簡潔,沒有繁瑣的業務細節。,應用的功能特性被切分爲各個大大小小的模塊,邏輯結構很是清晰。

Angular 已經封裝了很多經常使用的模塊,如:

  • ApplicationModule:封裝一些啓動相關的工具;
  • CommonModule:封裝一些經常使用的內置指令和內置管道等;
  • BrowserModule:封裝在瀏覽器平臺運行時的一些工具庫,同時將 CommonModule 和 ApplicationModule 打包導出,因此一般在使用時引入 BrowserModule 就能夠了;
  • FormsModule 和 ReactiveFormsModule:封裝表單相關的組件指令等;
  • RouterModule:封裝路由相關的組件指令等;
  • HttpModule:封裝網絡請求相關的服務等。

因此,若是你想使用 ngIf 和 ngStyle 等這些內置指令,記得先導入 CommonModule,其餘的模塊使用方法一致。

應用啓動

上述已說起,Angular 經過引導運行根模塊來啓動應用,引導的方式有兩種:動態引導和靜態引導。要理解兩者的區別,先來簡述 Angular 應用的啓動過程,Angular 應用運行以前,都須要通過編譯器對模塊、組件等進行編譯,編譯完後纔開始啓動應用並渲染界面。

動態引導和靜態引導的區別就在編譯的時機不一樣,動態引導是將全部代碼加載到瀏覽器後,在瀏覽器進行編譯;而靜態引導是將編譯過程前置到開發時的工程打包階段,加載到瀏覽器的將是編譯後的代碼。

假設咱們的根模塊爲 AppModule,動態引導的示例代碼以下:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app.module'; platformBrowserDynamic().bootstrapModule(AppModule);

動態引導是從 platformBrowserDynamic 函數啓動,該函數從 @angular/platform-browser-dynamic 文件模塊(關於 Angular 文件模塊將在下一小結講述)中導入。動態引導啓動的模塊 AppModule 便是咱們編寫的模塊,再來看看靜態引導的示例代碼:

import { platformBrowser } from '@angular/platform-browser'; import { AppModuleNgFactory } from './app.module.ngfactory'; platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);

靜態引導以 platformBrowser 函數啓動,這個函數是從 @angular/platform-browser 文件模塊中導入的,跟動態引導的不是同一個。靜態引導啓動的是 AppModuleNgFactory 模塊,這是 AppModule 通過編譯處理後生成的模塊(app.module 文件編譯後生成 app.module.ngfactory 文件)。因爲整個應用已經被預先編譯,因此編譯器並不會打包到項目代碼,代碼包體更小,加載更快,並且也省去了瀏覽器編譯這個步驟,所以應用啓動的速度也會更快。

動態引導開發流程簡單明瞭,適合小型項目或者大型應用的開發階段使用,而靜態引導須要在開發階段加入預編譯流程,稍顯複雜但性能提高明顯,任什麼時候候都推薦使用。

小結

這就是 Angular 的概覽,實際上它已不只僅是簡單的框架,更像是個平臺。不一樣的項目傾向的技術口味並不相同,技術選型時咱們都但願性價比最大化,不管是React,仍是Vue,抑或是Angular,都能解決咱們的主要問題,而Angular提供更普遍的多端支持,一站式解決方案,加上精心的架構設計、成熟的 Angular 生態、對標準的擁抱,還有 Google 和微軟的聯手支持,這些都給了開發者足夠的信心,Angular 將會是一個很是棒的平臺,不妨試試!半小時入門Angular 2