近期回答了 SF 上和 QQ 羣裏面的一些問題,發現一些問題對初學者來講,均可能會遇到。我的精力有限,建了個羣有興趣的朋友能夠加一下 QQ 羣:Angular 修仙之路(1)羣 - 153742079 (已滿),請加 Angular 修仙之路(2)羣 - 648681235。一塊兒維護這個 FAQ 哈!此外該文章也會持續更新,但願有興趣的讀者多提建議哈,謝謝!html
Github - angular-faqreact
面向對象編程中類的概念是什麼?git
面向對象編程中繼承的概念是什麼?程序員
什麼是接口?es6
什麼是泛型?github
什麼是枚舉?web
爲何 ES6 或 TypeScript 中的 Class 不會自動提高?typescript
TypeScrip 中類靜態屬性和成員屬性有什麼區別?編程
Pull 模式 與 Push 模式有什麼區別?bootstrap
Observable 與 Promise 有什麼區別?
RxJS 中 Subject 有什麼特色?
RxJS 中 BehaviorSubject、ReplaySubject、AsyncSubject 各有什麼做用?
Angular 1.x 與 Angular 4.x 區別大麼?
Angular 2.x 與 Angular 4.x 區別大麼?
AngularJS 1.x DI 系統有什麼問題?
constructor 與 ngOnInit 的應用場景是什麼?
ElementRef 有什麼做用?
Angular 開發 AppService 時,@Injectable() 是必須的麼?
Angular 中使用 [innerHtml] 時內容被轉義了要怎麼辦?
Angular 中 forwardRef 有什麼做用?
使用字符串做爲 Token 會有什麼問題?
Angular 中 Provider 的做用是什麼?
Angular 中配置 Provider 有哪幾種方式?
爲何 useClass 可使用簡潔的語法?
Angular 中 multi Provider 有什麼做用?
Angular 中 ViewEncapsulation 模式分爲哪幾種?
Angular 中宿主元素是什麼?
Angular 中組件如何實現繼承?
Angular 中如何傳遞異步數據?
Angular 組件通訊有哪些方式?
Angular 指令分爲哪幾種?
Angular 中指令與組件之間有什麼聯繫?
使用 [hidden]
屬性控制元素可見性有什麼問題?
自定義屬性指令中的 ElementRef
與 Renderer
有什麼做用?
自定義結構指令中的 TemplateRef
與 ViewContainerRef
有什麼做用?
註冊指令生命週期鉤子時,必定要實現對應的接口麼?
爲何在構造函數中是獲取不到輸入屬性的值?
裝飾器是什麼?
裝飾器有幾種分類?
@Component 中 @
的有什麼做用?
爲何在構造函數中,非 Type 類型的參數只能用 @Inject(Something) 的方式注入?
@Input 裝飾器的做用是什麼?
@Output 裝飾器的做用是什麼?
@Input 與 inputs 有什麼區別?
使用@Input 與 @Output 有什麼注意事項?
@ViewChild 與 @ViewChildren 裝飾器的做用是什麼?
@ContentChild 與 @ContentChildren 裝飾器的做用是什麼?
@ViewChild 與 @ContentChild 有什麼區別?
@HostListener 與 @HostBinding 有什麼用?
爲何在 Root Component 中沒法使用 ng-content
?
我應該把哪些類添加到declarations中?
我不該該把哪些類添加到declarations中?
爲何要把同一個組件聲明在不一樣的NgModule屬性中?
我應該導入什麼?
我應該導入 BrowserModule 仍是 CommonModule?
若是我兩次導入同一個模塊會怎麼樣?
我應該導出什麼?
我不該該導出什麼?
我能夠從新導出類和模塊麼?
forRoot() 方法是什麼?
爲何服務提供商在特性模塊中的任何地方都是可見的?
爲何在惰性加載模塊中聲明的服務提供商只對該模塊自身可見?
若是兩個模塊提供了同一個服務會怎麼樣?
咱們應該如何把服務的範圍限制到模塊中?
我應該把全應用級提供商添加到根模塊(AppModule)仍是根組件(AppComponent)中
我應該把其它提供商註冊到模塊中仍是組件中?
爲何 SharedModule 爲惰性加載模塊提供服務是個餿主意?
爲何惰性加載模塊會建立一個子注入器?
我要如何知道一個模塊或服務是否已經加載過了?
什麼是入口組件?
引導組件和入口組件有什麼不一樣?
何時我應該把組件添加到 entryComponents 中?
爲何 Angular 須要入口組件?
SharedModule 有什麼用?
CoreModule 有什麼用?
模板驅動與模型驅動表單各有什麼特色?
Angular 中如何使用模板驅動表單?
Angular 中如何使用模型驅動表單?
form 表單上的 novalidate
屬性做用是什麼?
表單控件的狀態除了 touched
外,還包含其它幾種狀態?
表單控件上 #userName
和 #userName="ngModel"
這兩種方式有什麼區別?
ngModelGroup 有什麼做用?
Angular 表單中 patchValue 與 setValue 方法有什麼區別?
Angular 中內置驗證器有哪一些?
Angular 中如何自定義驗證指令?
Angular 中如何自定義表單控件?
ChangeDetectionStrategy 變化檢測策略總共有幾種?
變化檢測器的狀態有哪幾種?
Angular 內置管道有哪一些?
Angular 中管道分爲哪幾類?
Angular 中怎麼定義一個管道?
Frameworks
Blogs
Tools
Platforms
雖然 JavaScript 中有類的概念,可是可能大多數 JavaScript 程序員並非很是熟悉類,這裏對類相關的概念作一個簡單的介紹。
類 (Class):一種面向對象計算機編程語言的構造,是建立對象的藍圖,描述了所建立的對象共同的屬性和方法。
對象 (Object):類的實例,經過 new
建立
面向對象 (OOP) 的三大特性:封裝、繼承、多態
封裝 (Encapsulation):將對數據的操做細節隱藏起來,只暴露對外的接口。外界調用端不須要知道細節,就能經過對外提供的接口來訪問該對象,同時也保證了外界沒法任意更改對象內部的數據
繼承 (Inheritance):子類繼承父類,子類除了擁有父類的全部特性外,還能夠擴展自有的功能特性
多態 (Polymorphism):由繼承而產生了相關的不一樣的類,對同一個方法能夠有不一樣的響應。好比 Cat
和 Dog
都繼承自 Animal
,可是分別實現了本身的 eat()
方法。此時針對某一個實例,咱們無需瞭解它是 Cat
仍是 Dog
,就能夠直接調用 eat()
方法,程序會自動判斷出來應該如何執行 eat()
存取器(getter & setter):用於屬性的讀取和賦值
修飾符(Modifiers):修飾符是一些關鍵字,用於限定成員或類型的性質。好比 public
表示公有屬性或方法
抽象類(Abstract Class):抽象類是供其餘類繼承的基類,抽象類不容許被實例化。抽象類中的抽象方法必須在子類中被實現
接口(Interfaces):不一樣類之間公有的屬性或方法,能夠抽象成一個接口。接口能夠被類實現(implements)。一個類只能繼承自另外一個類,可是能夠實現多個接口。
繼承(英語:inheritance)是面向對象軟件技術當中的一個概念。若是一個類別A 「繼承自」 另外一個類別B,就把這個A稱爲 「B的子類別」,而把B稱爲「A的父類別」也能夠稱 「B是A的超類」。繼承可使得子類別具備父類別的各類屬性和方法,而不須要再次編寫相同的代碼。在令子類別繼承父類別的同時,能夠從新定義某些屬性,並重寫某些方法,即覆蓋父類別的原有屬性和方法,使其得到與父類別不一樣的功能。另外,爲子類別追加新的屬性和方法也是常見的作法。 通常靜態的面向對象編程語言,繼承屬於靜態的,意即在子類別的行爲在編譯期就已經決定,沒法在執行期擴充。 —— 維基百科
繼承 (Inheritance) 是一種聯結類與類的層次模型。指的是一個類 (稱爲子類、子接口) 繼承另外的一個類 (稱爲父類、父接口) 的功能,並能夠增長它本身的新功能的能力,繼承是類與類或者接口與接口之間最多見的關係;繼承是一種 is-a 關係。
在面嚮對象語言中,接口(Interfaces)是一個很重要的概念,它是對行爲的抽象,而具體如何行動須要由類(classes)去實現(implements)。
TypeScript 中的接口是一個很是靈活的概念,除了可用於對類的一部分行爲進行抽象之外,也經常使用於對「對象的形狀(Shape)」進行描述。
interface Person { name: string; age: number; } let semlinker: Person = { name: 'semlinker', age: ** };
interface Person { readonly name: string; age?: number; }
接口可以描述 JavaScript 中對象擁有的各類各樣的外形。 除了描述帶有屬性的普通對象外,接口也能夠描述函數類型。
interface SearchFunc { (source: string, subString: string): boolean; } let mySearch: SearchFunc; mySearch = function(source: string, subString: string) { let result = source.search(subString); return result > -1; }
詳細的內容能夠參考 - typescript-handbook-interface
泛型(Generics)是容許同一個函數接受不一樣類型參數的一種模板。相比於使用 any 類型,使用泛型來建立可複用的組件要更好,由於泛型會保留參數類型。
interface GenericIdentityFn<T> { (arg: T): T; }
class GenericNumber<T> { zeroValue: T; add: (x: T, y: T) => T; } let myGenericNumber = new GenericNumber<number>(); myGenericNumber.zeroValue = 0; myGenericNumber.add = function(x, y) { return x + y; };
// Hero接口定義 interface Hero { id: number; name: string; } getHeroes(): Observable<Hero[]> { return Observable.of([ { id: 1, name: 'Windstorm' }, { id: 13, name: 'Bombasto' }, { id: 15, name: 'Magneta' }, { id: 20, name: 'Tornado' } ]); }
上面 getHeroes(): Observable<Hero[]>
表示調用 getHeroes()
方法後返回的是一個 Observable 對象,<Hero[]>
用於表示該 Observable 對象的觀察者,將會收到的數據類型。示例中表示將會返回 <Hero[]>
英雄列表。
枚舉用來定義一些有名字的數字常量。枚舉經過 enum
關鍵字來定義。
// angular2/packages/http/src/enums.ts 片斷 export enum RequestMethod { Get, // 0 Post, // 1 Put, // 2 Delete, // 3 Options, // 4 Head, // 5 Patch // 6 }
編譯後的 ES 5 代碼
var RequestMethod; (function (RequestMethod) { RequestMethod[RequestMethod["Get"] = 0] = "Get"; RequestMethod[RequestMethod["Post"] = 1] = "Post"; RequestMethod[RequestMethod["Put"] = 2] = "Put"; RequestMethod[RequestMethod["Delete"] = 3] = "Delete"; RequestMethod[RequestMethod["Options"] = 4] = "Options"; RequestMethod[RequestMethod["Head"] = 5] = "Head"; RequestMethod[RequestMethod["Patch"] = 6] = "Patch"; })(RequestMethod = exports.RequestMethod || (exports.RequestMethod = {}));
由於當 class 使用 extends 關鍵字實現繼承的時候,咱們不能確保所繼承父類是有效的,那麼就可能致使一些沒法預知的行爲。詳細的內容能夠參考 - Angular 2 Forward Reference 這篇文章。
AppComponent.ts
class AppComponent { static type: string = 'component'; name: string; constructor() { this.name = 'AppComponent'; } }
TS 編譯成 ES5 的代碼:
var AppComponent = (function () { function AppComponent() { this.name = 'AppComponent'; } return AppComponent; }()); AppComponent.type = 'component';
經過轉換後的代碼,咱們能夠知道類中的靜態屬性是做爲 AppComponent 構造函數的一個屬性,而成員屬性是屬於 AppComponent 實例。
Pull 和 Push 是數據生產者和數據的消費者兩種不一樣的交流方式。
在 "拉" 體系中,數據的消費者決定什麼時候從數據生產者那裏獲取數據,而生產者自身並不會意識到何時數據將會被髮送給消費者。
每個 JavaScript 函數都是一個 "拉" 體系,函數是數據的生產者,調用函數的代碼經過 ''拉出" 一個單一的返回值來消費該數據。
const add = (a, b) => a + b; let sum = add(3, 4);
ES6介紹了 iterator迭代器 和 Generator生成器 — 另外一種 "拉" 體系,調用 iterator.next()
的代碼是消費者,可從中拉取多個值。
在 "推" 體系中,數據的生產者決定什麼時候發送數據給消費者,消費者不會在接收數據以前意識到它將要接收這個數據。
Promise(承諾) 是當今 JS 中最多見的 "推" 體系,一個Promise (數據的生產者)發送一個 resolved value (成功狀態的值)來執行一個回調(數據消費者),可是不一樣於函數的地方的是:Promise 決定着什麼時候數據才被推送至這個回調函數。
RxJS 引入了 Observables (可觀察對象),一個全新的 "推" 體系。一個可觀察對象是一個產生多值的生產者,當產生新數據的時候,會主動 "推送給" Observer (觀察者)。
生產者 | 消費者 | |
---|---|---|
pull拉 | 被請求的時候產生數據 | 決定什麼時候請求數據 |
push推 | 按本身的節奏生產數據 | 對接收的數據進行處理 |
Observable(可觀察對象)是基於推送(Push)運行時執行(lazy)的多值集合。
MagicQ | 單值 | 多值 |
---|---|---|
拉取(Pull) | 函數 | 遍歷器 |
推送(Push) | Promise | Observable |
Promise
返回單個值
不可取消的
Observable
隨着時間的推移發出多個值
能夠取消的
支持 map、filter、reduce 等操做符
延遲執行,當訂閱的時候纔會開始執行
Subject 實際上是觀察者模式的實現,因此當觀察者訂閱 Subject 對象時,Subject 對象會把訂閱者添加到觀察者列表中,每當有 subject 對象接收到新值時,它就會遍歷觀察者列表,依次調用觀察者內部的 next()
方法,把值一一送出。
Subject 既是 Observable 對象,又是 Observer 對象,當有新消息時,Subject 會對內部的 observers 列表進行組播 (multicast)。Subject 之因此具備 Observable 中的全部方法,是由於 Subject 類繼承了 Observable 類,在 Subject 類中有五個重要的方法:
next - 每當 Subject 對象接收到新值的時候,next 方法會被調用
error - 運行中出現異常,error 方法會被調用
complete - Subject 訂閱的 Observable 對象結束後,complete 方法會被調用
subscribe - 添加觀察者
unsubscribe - 取消訂閱 (設置終止標識符、清空觀察者列表)
詳細的內容能夠參考 - RxJS - Subject
有些時候咱們會但願 Subject 能保存當前的最新狀態,而不是單純的進行事件發送,也就是說每當新增一個觀察者的時候,咱們但願 Subject 可以當即發出當前最新的值,而不是沒有任何響應。
有些時候咱們但願在 Subject 新增訂閱者後,能向新增的訂閱者從新發送最後幾個值,這時咱們就可使用 ReplaySubject 。
AsyncSubject 相似於 last
操做符,它會在 Subject 結束後發出最後一個值。
詳細的內容能夠參考 - RxJS - Subject
使人抓狂的是,這變化是 "驚天地泣鬼神"。
數據綁定機制
去除 $scope
變化檢測 - 使用 Zone.js,檢測變化(主要是 Event、Timer、XHR 這些事件源,引發的變化),不須要再使用 $scope.$watch()、$scope.$apply()、$timeout() 等方法。
從新設計的 DI 系統,解決 AngularJS 1.x DI 系統中存在的問題
組件通訊方面,移除了 $broadcast() 、$emit() 等方法
從新設計了指令系統,讓開發自定義指令變得更簡單
引入了全新的組件和模塊化方案,且 2.3 版本之後還支持組件繼承
引入了 Render 渲染層,可以更好地實現跨平臺
引入了視圖封裝機制,能夠靈活地控制視圖渲染方式
路由支持模塊懶加載、預加載等功能
編譯器支持 AOT 模式,大大減小了包的大小和提升了應用的性能
使人欣慰的是,Angular 4.x 向後兼容 Angular 2.x 版本。
體積更小,速度更快 - Angular應用程序變得更小更快,而且在將來幾個月將進一步改進框架。
向後兼容 - 該版本向後兼容 2.x 系列
更好的模板引擎 - 改進了AoT,將生成的代碼的大小減小約60%。若是模板越複雜,那麼優化的代碼也會越多。
動畫模塊改進 - 將動畫部分從@angular/core拆分出來,單獨打包。將核心模塊精簡後,在不使用動畫時產品中將不包含冗餘的動畫代碼。若是須要動畫,可以使用相關功能自行導入。
優化了內置指令nglf和ngFor
服務端渲染(Angular Universal)
TypeScript 2.1 和 2.2 的兼容
模板的Source Maps
建議新人能夠直接學習 Angular 4.x 的內容。
詳細的內容能夠參考 - Angular4.0.0正式發佈,附新特性及升級指南
內部緩存: angular1 應用程序中全部的依賴項都是單例,咱們不能控制是否使用新的實例
命名空間衝突: 在系統中咱們使用字符串來標識 service 的名稱,假設咱們在項目中已有一個 CarService,然而第三方庫中也引入了一樣的服務,這樣的話就容易出現混淆
DI 耦合度過高: angular1 中 DI 功能已經被框架集成了,咱們不能單獨使用它的 DI 特性
未能和模塊加載器結合: 在瀏覽器環境中,不少場景都是異步的過程,咱們須要的依賴模塊並非一開始就加載好的,或許咱們在建立的時候纔會去加載依賴模塊,再進行依賴建立,而 angualr 的 IoC 容器無法作到這點。
在 Angular 中 constructor 通常用於依賴注入或執行簡單的數據初始化操做,ngOnInit 鉤子主要用於執行組件的其它初始化操做或獲取組件輸入的屬性值。
export class AppComponent { name: string = ''; constructor(public elementRef: ElementRef) { // 使用構造注入的方式注入依賴對象 this.name = 'Semlinker'; // 執行初始化操做 } }
詳細的內容能夠參考 - Angular 2 constructor & ngOnInit
在應用層直接操做 DOM,就會形成應用層與渲染層之間強耦合,致使咱們的應用沒法運行在不一樣環境,如 web worker
中,由於在 web worker 環境中,是不能直接操做 DOM。有興趣的讀者,能夠閱讀一下 Web Workers 中支持的類和方法 這篇文章。經過 ElementRef 咱們就能夠封裝不一樣平臺下視圖層中的 native 元素 (在瀏覽器環境中,native 元素一般是指 DOM 元素),最後藉助於 Angular 2 提供的強大的依賴注入特性,咱們就能夠輕鬆地訪問到 native 元素。
詳細的內容能夠參考 - Angular 2 ElementRef
若是 AppService 不依賴於其餘對象,是能夠不用使用 Injectable 類裝飾器。當 AppService 須要在構造函數中注入依賴對象,就須要使用 Injectable 類裝飾器。比較推薦的作法無論是否有依賴對象,service 中都使用 Injectable 類裝飾器。
Angular 中默認將全部輸入值視爲不受信任。當咱們經過 property,attribute,樣式,類綁定或插值等方式,將一個值從模板中插入到DOM中時,Angular 會自幫咱們清除和轉義不受信任的值。此時,咱們 DomSanitizer
對象中,提供的 sanitize()
方法來解決上述問題。
詳細的內容能夠參考 - Angular 2 DomSanitizer
Angular 經過引入 forwardRef 讓咱們能夠在使用構造注入時,使用還沒有定義的依賴對象類型。下面咱們先看一下若是沒有使用 forwardRef ,在開發中可能會遇到的問題:
@Injectable() class Socket { constructor(private buffer: Buffer) { } } console.log(Buffer); // undefined @Injectable() class Buffer { constructor(@Inject(BUFFER_SIZE) private size: Number) { } } console.log(Buffer); // [Function: Buffer]
若運行上面的例子,將會拋出如下異常:
Error: Cannot resolve all parameters for Socket(undefined). Make sure they all have valid type or annotations
那麼要解決上面的問題,最簡單的處理方式是交換類定義的順序。除此以外,咱們還可使用 Angular 提供的 forward reference 特性來解決問題,具體以下:
import { forwardRef } from'@angular2/core'; @Injectable() class Socket { constructor(@Inject(forwardRef(() => Buffer)) private buffer) { } } class Buffer { constructor(@Inject(BUFFER_SIZE) private size: Number) { } }
詳細的內容能夠參考 - Angular 2 Forward Reference
Token 的做用是用來標識依賴對象,Token值多是 Type、InjectionToken、OpaqueToken 類的實例或字符串。一般不推薦使用字符串,由於若是使用字符串存在命名衝突的可能性比較高。在 Angular 4.x 之前的版本咱們通常使用 OpaqueToken 來建立 Token,而在 Angular 4.x 以上的版本版本,推薦使用 InjectionToken 來建立 Token 。
詳細的內容能夠參考 - 如何解決 Angular 2 中 Provider 命名衝突。
Provider 是用來描述與 Token 關聯的依賴對象的建立方式。當咱們使用 Token 向 DI 系統獲取與之相關連的依賴對象時,DI 會根據已設置的建立方式,自動的建立依賴對象並返回給使用者。
export interface ClassProvider { // 用於設置與依賴對象關聯的Token值,Token值多是Type、InjectionToken、OpaqueToken的實例或字符串 provide: any; useClass: Type<any>; // 用於標識是否multiple providers,如果multiple類型,則返回與Token關聯的依賴對象列表 multi?: boolean; } export interface ValueProvider { provide: any; useValue: any; multi?: boolean; } export interface ExistingProvider { provide: any; useExisting: any; multi?: boolean; } export interface FactoryProvider { provide: any; useFactory: Function; deps?: any[]; // 用於設置工廠函數的依賴對象 multi?: boolean; }
Angular 中配置 Provider 有四種方式,它們分別爲:
useClass
useValue
useExisting
useFactory
{ provide: ApiService, useClass: ApiService } // 可以使用簡潔的語法,即直接使用ApiService
{ provide: 'API_URL', useValue: 'http://my.api.com/v1' }
{ provide: 'ApiServiceAlias', useExisting: ApiService }
export function configFactory(config: AppConfig) { return () => config.load(); } { provide: APP_INITIALIZER, useFactory: configFactory, deps: [AppConfig], multi: true }
當 DI 解析 Providers 時,都會對提供的每一個 provider 進行規範化處理,即轉換成標準的形式。具體以下:
function _normalizeProviders(providers: Provider[], res: Provider[]): Provider[] { providers.forEach(b => { if (b instanceof Type) { // 支持簡潔的語法,轉換爲標準格式 res.push({provide: b, useClass: b}); } ... }); return res; }
首先咱們先來分析一下,若沒有設置 multi: true 屬性時,使用同一個 token 註冊 provider 時,會出現什麼問題?
class Engine { } class TurboEngine { } var injector = ReflectiveInjector.resolveAndCreate([ provide(Engine, {useClass: Engine}), provide(Engine, {useClass: TurboEngine}) ]); var engine = injector.get(Engine); // engine instanceof TurboEngine == true
這說明若是使用同一個 token 註冊 provider,後面註冊的 provider 將會覆蓋前面已註冊的 provider。此外,Angular 2 使用 multi provider 的這種機制,爲咱們提供可插拔的鉤子(pluggable hooks) 。另外須要注意的是,multi provider是不能和普通的 provider 混用。
詳細的內容能夠參考 - Angular 2 Multi Providers
ViewEncapsulation 容許設置三個可選的值:
ViewEncapsulation.Emulated - 無 Shadow DOM,可是經過 Angular 提供的樣式包裝機制來封裝組件,使得組件的樣式不受外部影響。這是 Angular 的默認設置。
ViewEncapsulation.Native - 使用原生的 Shadow DOM 特性
ViewEncapsulation.None - 無 Shadow DOM,而且也無樣式包裝
詳細的內容能夠參考 - Angular 2 ViewEncapsulation
宿主元素的概念同時適用於指令和組件。對於指令來講,這個概念是至關簡單的。應用指令的元素,就是宿主元素。假設咱們已聲明瞭一個 HighlightDirective 指令 (selector: '[exeHighlight]'):
<p exeHighlight> <span>高亮的文本</span> </p>
上面 html 代碼中,p
元素就是宿主元素。若是該指令應用於自定義組件中如:
<exe-counter exeHighlight> <span>高亮的文本</span> </exe-counter>
此時 exe-counter
自定義元素,就是宿主元素。
詳細的內容能夠參考 - Angular 2 HostListener & HostBinding
組件繼承涉及如下的內容:
Metadata:如 @Input()
、@Output()
、@ContentChild/Children
、@ViewChild/Children
等。在派生類中定義的元數據將覆蓋繼承鏈中的任何先前的元數據,不然將使用基類元數據。
Constructor:若是派生類未聲明構造函數,它將使用基類的構造函數。這意味着在基類構造函數注入的全部服務,子組件都能訪問到。
Lifecycle hooks:若是基類中包含生命週期鉤子,如 ngOnInit、ngOnChanges 等。儘管在派生類沒有定義相應的生命週期鉤子,基類的生命週期鉤子會被自動調用。
須要注意的是,模板是不能被繼承的 ,所以共享的 DOM 結構或行爲須要單獨處理。
詳細的內容能夠參考 - Angular 2 Component Inheritance
在父子組件通訊時,若是輸入屬性綁定的數據是異步的,那咱們可使用 *ngIf、ngOnChanges、Observable 等方案解決上述問題。
詳細的內容能夠參考 - Angular 2 Pass Async Data
組件通訊的經常使用方式:@Input、@Output、@ViewChild、模板變量、MessageService、Broadcaster (Angular 1.x $rootScope 中 $on、$broadcast ) 等。
詳細的內容能夠參考 - Angular 2 Components Communicate
組件(Component directive):用於構建UI組件,繼承於 Directive 類
屬性指令(Attribute directive): 用於改變組件的外觀或行爲
結構指令(Structural directive): 用於動態添加或刪除DOM元素來改變DOM佈局
詳細的內容能夠參考 - Angular 2 Directive
組件繼承於指令,並擴展了與 UI 視圖相關的屬性,如 template、styles、animations、encapsulation 等。
詳細的內容能夠參考 - Angular 2 Directive Lifecycle
[hidden]
屬性控制元素可見性有什麼問題?<div [hidden]="!showGreeting"> Hello, there! </div>
當在對應的 DOM 元素上設置 display: flex
屬性時,儘管[hidden]
對應的表達式爲 true
,但元素卻能正常顯示。對於這種特殊狀況,則推薦使用 *ngIf
。
ElementRef
與 Renderer
有什麼做用?爲了可以支持跨平臺,Angular 2 經過抽象層封裝了不一樣平臺的差別,統一了 API 接口。如定義了抽象類 Renderer 、抽象類 RootRenderer 等。此外還定義瞭如下引用類型:ElementRef、TemplateRef、ViewRef 、ComponentRef 和 ViewContainerRef 等。
詳細內容請參考 - Angular 2 ElementRef
TemplateRef
與 ViewContainerRef
有什麼做用?用於表示內嵌的 template 模板元素,經過 TemplateRef 實例,咱們能夠方便建立內嵌視圖(Embedded Views),且能夠輕鬆地訪問到經過 ElementRef 封裝後的 nativeElement。須要注意的是組件視圖中的 template 模板元素,通過渲染後會被替換成 comment 元素。
用於表示一個視圖容器,可添加一個或多個視圖。通ViewContainerRef 實例,咱們能夠基於 TemplateRef 實例建立內嵌視圖,並能指定內嵌視圖的插入位置,也能夠方便對視圖容器中已有的視圖進行管理。簡而言之,ViewContainerRef 的主要做用是建立和管理內嵌視圖或組件視圖。
詳細的內容能夠參考 - Angular 2 TemplateRef & ViewContainerRef
註冊指令生命週期鉤子時,實現對應的接口不是必須的,接口能夠幫助咱們在開發階段儘早地發現錯誤,由於咱們有可能在註冊生命週期鉤子的時候,寫錯了某個鉤子的名稱,在運行時可能不會拋出任何異常,但頁面顯示卻不是預期的效果,所以建議讀者仍是遵照該開發規範。另外還要注意的一點是,TypeScript 中定義的接口,是不會編譯生成 ES5 相關代碼,它只用於編譯階段作校驗。
在子組件的構造函數中,是沒法獲取輸入屬性的值,只能在 ngOnChanges 或 ngOnInit 鉤子中獲取到。由於子組件的構造函數會優先執行,當子組件輸入屬性變化時會自動調用 ngOnChanges 鉤子,而後在調用 ngOnInit 鉤子,因此在 ngOnInit 鉤子內能獲取到輸入的屬性。
詳細的內容能夠參考 - Angular 2 constructor & ngOnInit
它是一個表達式
該表達式被執行後,返回一個函數
函數的入參分別爲 targe、name 和 descriptor
執行該函數後,可能返回 descriptor 對象,用於配置 target 對象
類裝飾器 (Class decorators)
屬性裝飾器 (Property decorators)
方法裝飾器 (Method decorators)
參數裝飾器 (Parameter decorators)
詳細的內容能夠參考 - Angular 2 Decorators - 1
@
的有什麼做用?@Component 中 @
是語法糖,用於表示裝飾器。
import { Component } from '@angular/core'; @Component({ selector: 'exe-app', template: ` <h1>Hello Angular</h1> `, }) export class AppComponent { constructor() { } }
編譯後 ES 5 代碼:
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {}; define(["require", "exports", "@angular/core"], function (require, exports, core_1) { "use strict"; var AppComponent = (function () { function AppComponent() { } return AppComponent; }()); AppComponent = __decorate([ core_1.Component({ selector: 'exe-app', template: "\n <h1>Hello Angular</h1>\n ", }) ], AppComponent); exports.AppComponent = AppComponent; });
@Component 中 @ 符號的做用是爲了告訴 TypeScript 編譯器,@ 後面的是裝飾器函數或裝飾器工廠,須要特殊處理。假設在 @Component({...}) 中去掉 @ 符號,那麼變成了普通的函數調用,這樣立刻就會報錯,由於咱們並無定義 Component 函數。經過觀察轉換後的代碼,咱們發現 @Component({...}) 被轉換成 core_1.Component ,它就是從 @angular/core
導入的裝飾器函數。
由於只有是 Type 類型的對象,纔會被 TypeScript 編譯器編譯。
詳細的內容能夠參考 - Angular 2 Inject
@Input 是屬性裝飾器,用來定義組件內的輸入屬性。在實際應用場合,咱們主要用來實現父組件向子組件傳遞數據。Angular 應用是由各式各樣的組件組成,當應用啓動時,Angular 會從根組件開始啓動,並解析整棵組件樹,數據由上而下流下下一級子組件。
詳細的內容能夠參考 - Angular 2 Input
Output 是屬性裝飾器,用來定義組件內的輸出屬性。在 Angular 2 Input 文章中,咱們介紹了 Input 裝飾器的做用,也瞭解了當應用啓動時,Angular 會從根組件開始啓動,並解析整棵組件樹,數據由上而下流下下一級子組件。而咱們今天介紹的 Output 裝飾器,是用來實現子組件將信息經過事件的形式通知到父級組件。具體以下圖所示:
詳細的內容能夠參考 - Angular 2 Output
相同點:
它們都是用來定義輸入屬性
不一樣點:
inputs 定義在指令的 metadata 信息中,開發者對指令的輸入屬性一目瞭然。此外對於未選用 TypeScript 做爲開發語言的開發者,也只能在 metadata 中定義指令的輸入屬性。
@Input 屬於屬性裝飾器,經過它咱們能夠一塊兒定義屬性的訪問描述符 (public、private、protected):
@Input() public attr: string;
堅持使用 @Input 和 @Output ,而非 @Directive 和 @Component 裝飾器的 inputs 和 outputs 屬性:
易於在類裏面識別哪些屬性是輸入屬性或輸出屬性。
堅持把 @Input 或者 @Output 放到所裝飾的屬性的同一行:
若是須要重命名與@Input
或者@Output
關聯的屬性或事件名,你能夠在一個位置修改。
避免爲輸入和輸出屬性指定別名
同一個屬性有兩個名字(一個對內一個對外)很容易致使混淆。
詳細的內容能夠參考 - Angular 2 風格指南 - STYLE 05-12
@ViewChild 屬性裝飾器,用來從模板視圖中獲取匹配的元素。它支持 Type 類型或 string 類型的選擇器,同時支持設置 read 查詢條件,以獲取不一樣類型的實例。
@ViewChildren 屬性裝飾器是用來從模板視圖中獲取匹配的多個元素,返回的結果是一個 QueryList 集合。
詳細的內容能夠參考 - Angular 2 ViewChild & ViewChildren
@ContentChild 屬性裝飾器,用來從經過 Content Projection 方式 (ng-content) 設置的視圖中獲取匹配的元素。
@ContentChild 屬性裝飾器,從經過 Content Projection 方式設置的視圖中獲取匹配的多個元素,返回的結果是一個 QueryList 集合。
詳細的內容能夠參考 - Angular 2 ContentChild & ContentChildren
相同點
都是屬性裝飾器
都有對應的複數形式裝飾器:ContentChildren、ViewChildren
都支持 Type<any>
|Function|string 類型的選擇器
不一樣點
ContentChild 用來從經過 Content Projection 方式 (ng-content) 設置的視圖中獲取匹配的元素
ViewChild 用來從模板視圖中獲取匹配的元素
在父組件的 ngAfterContentInit 生命週期鉤子中才能成功獲取經過 ContentChild 查詢的元素
在父組件的 ngAfterViewInit 生命週期鉤子中才能成功獲取經過 ViewChild 查詢的元素
@HostListener 是屬性裝飾器,用來爲宿主元素添加事件監聽。而 @HostBinding 也是屬性裝飾器,用來動態設置宿主元素的屬性值。
詳細的內容能夠參考 - Angular 2 HostListener & HostBinding
ng-content
?緣由主要有如下幾點:
<my-app></my-app>
標籤之間的信息是用來代表 Angular 的應用程序正在啓動中
Angular 2 編譯器不會處理 index.html
文件中設置的綁定信息,另外出於安全因素考慮,爲了不 index.html
中的 {{}}
插值,被服務端使用的模板引擎處理。
能夠把可聲明的類添加到模塊 declarations
列表中。可聲明的類是指:組件、指令和管道。
這些類只能在應用程序的一個而且只有一個模塊中聲明。只有當它們從屬某個模塊時,才能在此模塊中聲明它們。
只有可聲明的類才能添加到模塊的 declarations
列表中。如下類不能添加到 declarations
列表中:
已經在其它模塊中聲明過的類。不管它來自應用內模塊仍是第三方模塊
從其它模塊中導入的指令
模塊類
服務類
非 Angular 的類和對象,好比:字符串、數字、函數、實體模型、配置、業務邏輯和輔助類
咱們常常看到 AppComponent
同時出如今 declarations
和 bootstrap
中。咱們還能夠看到 HeroComponent
也同時出如今 declarations
、exports
和 entryComponent
中。
這看起來是多餘的,但由於不一樣的列表有不一樣的意義,若是僅在一個列表中聲明,咱們沒法推斷它是否出如今其它列表中。
一句話:導入你須要在當前模塊的組件模板中使用的那些公開的 (被導出的) 可聲明類。
這意味着要從 @angular/common
中導入 CommonModule
才能訪問 Angular 的內置指令,如 *ngIf
和 *ngFor
。若是你的組件中須要使用 [(ngModel)]
雙向綁定的功能,就須要從 @angular/forms
中導入 FormsModule
。
須要注意的是,只能在根模塊 (AppModule) 中導入 BrowserModule
。
幾乎全部要在瀏覽器中使用的應用的根模塊 (AppModule ) 都應該從@angular/platform-browser
模塊中導入BrowserModule
。
BrowserModule 提供了瀏覽器平臺下運行的基礎指令和服務,該模塊內部從新導出了 CommonModule,這意味着 AppModule 中組件也能夠訪問 Angular 的內置指令。
在其它任何模塊中都不要導入 BrowserModule
。在特性模塊和惰性加載模塊中應該導入 CommonModule
。由於它們只須要 Angular 中提供的內置指令,而不須要從新初始化應用級的服務。
特性模塊中導入 CommonModule
可讓它能用在任何目標平臺上,不只是瀏覽器平臺。
不會有問題。當三個模塊全都導入模塊 'A' 時,Angular 只會首次遇到時加載一次模塊 'A',以後就不會這麼作了。
不管A
出如今所導入模塊的哪一個層級,都會如此。 若是模塊 'B' 導入模塊 'A'、模塊 'C' 導入模塊 'B',模塊 'D' 導入[C, B, A]
,那麼 'D' 會觸發模塊 'C' 的加載,'C' 會觸發 'B' 的加載,而 'B' 會加載 'A'。 當Angular在 'D' 中想要獲取 'B' 和 'A' 時,這兩個模塊已經被緩存過了,能夠當即使用。
Angular 不容許模塊之間出現循環依賴,因此不要讓模塊 'A' 導入模塊 'B',而模塊 'B' 又導入模塊 'A'。
導出其它模塊須要引用的可聲明類,若是你不導出某個類,它就是私有的,只對當前模塊中聲明的其它組件可見。
你能夠導出任何可聲明類 (組件、指令和管道),而不用管它是聲明在當前模塊中仍是某個導入的模塊中。
你也能夠從新導出整個已導入的模塊,這將致使從新導出模塊中導出的全部類。模塊甚至還能夠導出它不曾導入過的模塊。
不要導出的:
那些你只想在當前模塊中使用的私有組件、指令和管道。若是你不但願在任何模塊看到它,就不要導出。
不可聲明的對象,好比服務、函數、配置、實體模型等
純服務的模塊沒有公開導出的聲明。例如,沒有必要從新導出 HttpModule
,由於它不導出任何東西。它惟一的用途就是把 Http 相關的服務註冊到應用中
模塊是從其它模塊中選取類並把它們從新導出成統1、便利的新模塊的最佳方式。
模塊能夠從新導出其它模塊,這會致使從新導出它們導出的全部類。Angular 的 BrowserModule 就從新導出了一組模塊,例如:
exports: [CommonModule, ApplicationModule]
不要費心去導出純服務類。純服務類的模塊不會導出任何可供其它模塊使用的可聲明類。例如,不用從新導出
HttpModule
,由於它沒有導出任何東西。它惟一的用途就是把 Http 相關的服務註冊到應用中。
靜態方法 forRoot()
是一個約定,它可讓開發人員更輕鬆的配置模塊的服務提供商。
RouterModule.forRoot()
就是一個很好的例子。咱們把一個 Routes
對象做爲參數傳給 RouterModule.forRoot()
方法,就是爲了配置應用級的路由。RouterModule.forRoot()
方法返回一個 ModuleWithProviders
對象,咱們把返回的對象添加到根模塊的 imports
列表中。
只能在應用的根模塊中調用並導入
forRoot()
方法返回的對象。在其它模塊中導入它,特別是惰性加載模塊中,是違反設計目標並會致使一個運行時異常。
RootModule
也提供了一個靜態方法 forChild
,用於配置惰性加載模塊的路由。
forRoot和forChild都是方法的約定名稱,它們分別用於在根模塊和特性模塊中配置服務。
列在引導模塊的 @NgModule.providers
中的服務提供商具備全應用級做用域。往 @NgModule.providers
中添加服務提供商將致使該服務被髮布到整個應用中。
當咱們導入一個模塊時,Angular 就會把該模塊的服務提供商 (也就是 providers 列表中的內容) 加入該應用的根注入器中,這會讓該服務提供商對應用中的全部模塊都是可見的。
Angular 就是如此設計的。 經過模塊導入來實現可擴展性是 Angular 模塊系統的主要設計目標。 把模塊的提供商併入應用程序的注入器可讓庫模塊使用新的服務來強化應用程序變得更容易。 只要添加一次 HttpModule
,那麼應用中的每一個組件就均可以發起 Http請求了。
不過,若是你指望模塊的服務只對那個特性模塊內部聲明的組件可見,那麼這可能會帶來一些問題。 若是 HeroModule
提供了一個 HeroService
,而且根模塊 AppModule
導入了HeroModule
,那麼任何知道 HeroService
類型的類均可能注入該服務,而不只是在 HeroModule
中聲明的那些類。
和啓動時就加載的模塊中的提供商不一樣,惰性加載模塊中的提供商是侷限於模塊自身。
當 Angular 路由器惰性加載一個模塊時,它建立了一個新的運行環境。這個環境擁有本身的注入器,它是應用注入器的直屬子級。
路由器把惰性加載模塊的提供商和它導入模塊的提供商添加到這個子注入器中。
這些提供商不會被擁有相同令牌的應用級別提供商的變化所影響。當路由器在惰性加載環境中建立組件時,Angular 會優先使用惰性加載模塊中的服務實例,而不是來自應用的根注入器。
當同時加載了兩個導入的模塊,它們都列出了使用同一個令牌的提供商時,後導入的模塊會 "獲勝",這是由於這兩個提供商都被添加到了同一個注入器中。
當 Angular 嘗試根據令牌注入服務時,它使用第二個提供商來建立並交付服務實例。
每一個注入了該服務的類得到的都是由第二個提供商建立的實例。 即便是聲明在第一個模塊中的類,它取得的實例也是來自第二個提供商的。
若是一個模塊在應用程序啓動時就加載,它的@NgModule.providers
具備全應用級做用域。 它們也可用於整個應用的注入中。
導入的提供商很容易被由其它導入模塊中的提供商替換掉。 雖然設計如此,可是也可能引發意料以外的結果。
做爲一個通用的規則,應該只導入一次帶提供商的模塊,最好在應用的根模塊中。 那裏也是配置、包裝和改寫這些服務的最佳位置。
假設模塊須要一個定製過的 HttpBackend
,它爲全部的 Http 請求添加一個特別的請求頭。 若是應用中其它地方的另外一個模塊也定製了HttpBackend
或僅僅導入了HttpModule
,它就會改寫當前模塊的HttpBackend
提供商,丟掉了這個特別的請求頭。 這樣服務器就會拒絕來自該模塊的請求。
要消除這個問題,就只能在應用的根模塊 AppModule
中導入 HttpModule
。
只要有可能,就讓模塊惰性加載。Angular 爲惰性加載模塊提供了獨立的子注入器。該模塊中的提供商只對由該注入器建立的組件樹可見。
繼續看了例子,假設某個模塊的組件真的須要一個私有的、自定義的 HttpBackend
。
那就建立一個 "頂級組件" 來扮演該模塊中全部組件的根。把這個自定義的 HttpBackend
提供商添加到這個頂級組件的 providers
列表中,而不是該模塊的 providers
中。回憶一下,Angular 會爲每一個組件實例建立一個子注入器,並使用組件本身的 providers
來配置這個注入器。
當該組件的子組件須要一個 HttpBackend
服務時,Angular 會提供一個局部的 HttpBackend
服務,而不是應用根注入器建立的那個服務。此時,子組件將正確發起 http 請求,而無論其它模塊對 HttpBackend
作了什麼?同時須要確保模塊中的組件都建立成這個頂級組件的子組件。
在根模塊 (AppModule) 中註冊全應用級提供商,而不是 AppComponent
中。
惰性加載的模塊及其組件能夠注入 AppModule
中的服務,卻不能注入 AppComponent
中註冊的服務。
只有當該服務必須對 AppComponent
組件樹以外的組件不可見時,才應該把服務註冊進 AppComponent
的 providers
中。但這是一個不常見的用法,正常狀況下,咱們是優先把服務提供商註冊進模塊中,而不是組件中。
Angular 把全部啓動期模塊的提供商都註冊進了應用的根注入器中。這些服務是由根注入器中的提供商建立的,而且在整個應用中均可用。它們具備應用級做用域。
某些服務 (好比 Router) 只有當註冊進應用的根注入器時才能正常工做。
相反,Angular 使用 AppComponent
本身的注入器註冊了 AppComponent
的提供商。AppComponent
服務只在該組件及其子組件樹中才能使用。 它們具備組件級做用域。
AppComponent
的注入器是根注入器的子級,注入器層次中的下一級。 這對於沒有路由器的應用來講幾乎是整個應用了。 但這個 "幾乎" 對於帶路有的應用仍然是不夠的。
一般,優先把模塊中具體特性的提供商註冊到模塊中 (@NgModule.providers
),而不是組件中 (@Component.providers
)。
當你必須把服務實例的範圍限制到某個組件及其子組件樹時,就把提供商註冊到該組件中。 指令的提供商也一樣照此處理。
須要注意的是,總在根模塊中註冊全應用級服務,而不要在根組件中。
假設把 UserService
列在了模塊的 providers
中。 假設每一個模塊都導入了這個SharedModule
模塊。導入 SharedModule
的每一個模塊都會設置 UserService
提供商。Angular 把它們中的一個註冊到根注入器中。當應用中的某些組件要求注入 UserService
,Angular 就會在應用的根注入器中查找它,並交付一個全應用級的 UserService
單例對象。
如今,該考慮惰性加載模塊了。當路由器惰性加載模塊時,它會建立一個子注入器,而且把 UserService
的提供商註冊到那個子注入器中。子注入器和根注入器是不一樣的。
當 Angular 建立一個惰性加載的組件時,它必須注入 UserService
服務。這時,它會從惰性加載模塊的子注入器查找 UserService
的提供商,並用它建立一個 UserService
的新實例。這個 UserService
實例與 Angular 在主動加載的組件中注入的那個全應用級單例對象大相徑庭。
Angular會把 @NgModule.providers
中的提供商添加到應用的根注入器中。除非該模塊是惰性加載的,這種狀況下,它會建立 一 子注入器,而且把該模塊的提供商添加到這個子注入器中。
爲何 Angular 不能像主動加載模塊那樣把惰性加載模塊的提供商也添加到應用程序的根注入器中呢?爲何會出現這種不一致?
歸根結底,這來自於 Angular 依賴注入系統的一個基本特徵: 在注入器尚未被第一次使用以前,能夠不斷爲其添加提供商。 一旦注入器已經建立和開始交付服務,它的提供商列表就被凍結了,再也不接受新的提供商。
當應用啓動時,Angular 會首先使用全部主動加載模塊中的提供商來配置根注入器,這發生在它建立第一個組件以及注入任何服務以前。 一旦應用開始工做,應用的根注入器就再也不接受新的提供商了。
以後,應用邏輯開始惰性加載某個模塊。 Angular 必須把這個惰性加載模塊中的提供商添加到某個注入器中。 可是它沒法將它們添加到應用的根注入器中,由於根注入器已經再也不接受新的提供商了。 因而,Angular 在惰性加載模塊的上下文中建立了一個新的子注入器。
某些模塊及其服務只能被根模塊 AppModule
加載一次。 在惰性加載模塊中再次導入這個模塊會致使錯誤的行爲,這個錯誤可能很是難於檢測和診斷。
爲了防範這種風險,咱們能夠寫一個構造函數,它會嘗試從應用的根注入器中注入該模塊或服務。若是這種注入成功了,那就說明這個類是被第二次加載的,咱們就能夠拋出一個錯誤,或者採起其它挽救措施。
某些 Angular 模塊 (例如 BrowserModule
) 就實現了構造函數守衛功能。
constructor (@Optional() @SkipSelf() parentModule: CoreModule) { if (parentModule) { throw new Error( 'CoreModule is already loaded. Import it in the AppModule only'); } }
Angular 中經過組件選擇器加載的組件不是入口組件。大多數應用組件都是聲明式加載的,Angular 使用該組件的選擇器在模板中定位元素,而後建立表現該組件的 HTML,並把它插入 DOM 中所選元素的內部。它們不是入口組件。
用於引導的根 AppComponent
就是一個入口組件。雖然它的選擇器匹配了 index.html
中的一個元素,可是 index.html
並非組件模板,並且 AppComponent
選擇器也不會在任何組件模板中出現。
Angular 老是會動態加載 AppComponent
—— 不管把它的類型列在了 @NgModule.bootstrap
函數中,仍是命令式的調用該模塊的ngDoBootstrap
方法來引導它。
在路由定義中用到的組件也一樣是入口組件。 路由定義根據類型來引用組件。 路由器會忽略路由組件的選擇器 (即便它有選擇器),而且把該組件動態加載到 RouterOutlet
元素中。
編譯器沒法經過在其它組件的模板中查找來發現這些入口組件。 咱們必須經過把它們加入 entryComponents
列表中來讓編譯器知道它們的存在。
Angular 會自動把下列類型的組件添加到模塊的 entryComponents
中:
那些出如今 @NgModule.bootstrap
列表中的組件。
那些被路由定義引用的組件。
引導組件是入口組件的一種。它是被 Angular 的引導 (應用啓動) 過程加載到 DOM 中的入口組件。 其它入口組件則是被其它方式動態加載的,好比被路由器加載。
@NgModule.bootstrap
屬性告訴編譯器這是一個入口組件,同時它應該生成一些代碼來用該組件引導此應用。
不須要把組件同時列在 bootstrap
和 entryComponent
列表中 —— 雖然這樣作也沒壞處。
大多數應用開發者都不須要把組件添加到 entryComponents
中。
Angular會自動把恰當的組件添加到入口組件中。 列在 @NgModule.bootstrap
中的組件會自動加入。 由路由配置引用到的組件會被自動加入。 用這兩種機制添加的組件在入口組件中佔了絕大多數。
若是你的應用要用其它手段來根據類型引導或動態加載組件,那就得把它顯式添加到 entryComponents
中。
入口組件也是被聲明的。爲何 Angular 編譯器不爲 @NgModule.declarations
中的每一個組件都生成一份代碼呢?那樣就不須要入口組件了。
緣由在於 tree shaking
。對於產品化的應用,咱們但願加載儘量小的代碼。 代碼中應該僅僅包括那些實際用到的類。 它應該排除那些咱們從未用過的組件,不管該組件是否被聲明過。
事實上,大多數庫中聲明和導出的組件咱們都用不到。若是咱們從未引用它們,那麼tree shaking
就會從最終的代碼包中把這些組件砍掉。
若是 Angular 編譯器爲每一個聲明的組件都生成了代碼,那麼 tree shaking
的優化做用就沒了。因此,編譯器轉而採用一種遞歸策略,它只爲咱們用到的那些組件生成代碼。
在實際項目中,咱們一般會有一些通用的組件、指令或管道。爲了便於統一的維護,咱們會定義一個 SharedModule
模塊,這種模塊應該只包含 declarations
聲明,而且應該導出幾乎全部的 declarations
聲明類。
SharedModule 也能夠從新導出其它模塊,好比 CommonModule
、FormsModule
和通用的模塊 (包含通用 UI組件)。
SharedModule 不該該帶有 providers,它導入或從新導出的模塊中,也不該該有 providers
。若是你要違背這條原則,請務必想清楚你在作什麼,並要有充分的理由。
在任何特性模塊中 (不管是你在應用啓動時主動加載的模塊仍是以後惰性加載的模塊),你均可以隨意導入這個SharedModule
。
爲了便於統一的管理系統中通用的服務,咱們能夠建立一個 CoreModule
模塊。咱們能夠把 CoreModule
當作一個沒有 declarations
的純服務模塊。
須要注意的是,只能在根模塊 (AppModule) 中導入 CoreModule
。永遠不要在除了根模塊以外的任何模塊導入 CoreModule
。
使用方便
適用於簡單的場景
經過 [(ngModel)] 實現數據雙向綁定
最小化組件類的代碼
不易於單元測試
比較靈活
適用於複雜的場景
簡化了HTML模板的代碼,把驗證邏輯抽離到組件類中
方便的跟蹤表單控件值的變化
易於單元測試
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; @NgModule({ imports: [BrowserModule, FormsModule], // we add FormsModule here declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {}
<form novalidate> <input type="text" name="name" ngModel required> <input type="text" name="street" ngModel minlength="3"> <input type="text" name="city" ngModel maxlength="10"> <input type="text" name="zip" ngModel pattern="[A-Za-z]{5}"> </form>
詳細的內容能夠參考 - Angular 4.x Template-Driven Forms
import { ReactiveFormsModule } from '@angular/forms'; @NgModule({ imports: [BrowserModule, ReactiveFormsModule], ... }) export class AppModule {}
@Component({ selector: 'exe-app', template: ` <form novalidate [formGroup]="form"> ... </form> ` }) class AppComponent { constructor(private fb: FormBuilder) {} ngOnInit() { this.form = this.fb.group({ name: ['', Validators.required], street: ['', Validators.minLength(3)], city: ['', Validators.maxLength(10)], zip: ['', Validators.pattern('[A-Za-z]{5}')] }); } }
詳細的內容能夠參考 - Angular 4.x Reactive Forms
novalidate
屬性做用是什麼?一般狀況下,咱們須要爲每一個 form
都添加 novalidate
屬性,該屬性用於禁用瀏覽器 native
表單驗證。若是想要開啓 native
表單驗證,只需添加 ngNativeValidate
屬性。
novalidate
<form novalidate></form>
ngNativeValidate
<form ngNativeValidate></form>
touched
外,還包含其它幾種狀態?表單控件有如下 6 種狀態,咱們能夠經過 #userName="ngModel"
方式獲取對應的狀態值。具體狀態以下:
valid - 表單控件有效
invalid - 表單控件無效
pristine - 表單控件值未改變
dirty - 表單控件值已改變
touched - 表單控件已被訪問過
untouched - 表單控件未被訪問過
#userName
和 #userName="ngModel"
這兩種方式有什麼區別?#userName
- 指向 input 表單控件
#userName="ngModel"
- 指向 NgModel 實例
ngModelGroup 指令是 Angular 提供的另外一特殊指令,能夠對錶單輸入內容進行分組,方便咱們在語義上區分不一樣性質的輸入。例如聯繫人的信息包括姓名及住址,如今需對姓名和住址進行精細化信息收集,姓名可精細化成姓和名字,地址可精細化成城市、區、街等。此時就能夠將姓名及住址進行分組收集,具體以下:
<form #concatForm = "ngForm"> <fieldset ngModelGroup="nameGroup" #nameGroup="ngModelGroup"> <label>姓:</label> <input type="text" name="firstname" [(ngModel)]="curContact.firstname" required> <label>名字:</label> <input type="text" name="lastname" [(ngModel)]="curContact.lastname" required> </fieldset> <fieldset ngModelGroup="addressGroup" #addressGroup ="ngModelGroup"> <label>街:</label> <input type="text" name="street" [(ngModel)]="curContact.street" required> <label>區:</label> <input type="text" name="zip" [(ngModel)]="curContact.zip" required> <label>城市:</label> <input type="text" name="city" [(ngModel)]="curContact.city" required> </fieldset> </form>
上述例子分別對聯繫人的姓名和住址進行分組, ngModelGroup 將姓和名字的表單內容進行包裹組成姓名分組,將城市、區和街道的表單內容進行包裹組成住址分組。此時concatForm.value值爲:
{ nameGroup: { firstname: '', lastname: '', }, addressGroup: { street: '', zip: '', city: '' } }
在 Angular 4.x 中有多種方式能夠更新表單的值,對於使用響應式表單的場景,咱們能夠經過框架內部提供的 API ,(如 patchValue 和 setValue )方便地更新表單的值。
對於 FormControl 對象來講,patchValue() 和 setValue() 這兩個方法是等價的。此外 setValue() 方法中作了三件事:
更新控件當前值
判斷是否註冊 onChange
事件,如有則循環調用已註冊的 changeFn
函數。
從新計算控件的值和驗證狀態
對於 FormGroup 對象來講, setValue() 方法相比 patchValue() 會更嚴格,會執行多個判斷:
判斷的是否爲全部控件都設置更新值
判斷控件是否存在
而 patchValue() 方法,會先使用 this.controls[name]
進行過濾,只更新參數 value
中設定控件的值。
詳細的內容能夠參考 - Angular 4.x Forms patchValue and setValue
目前 Angular 支持的內建 validators 以下:
required - 設置表單控件值是非空的
email - 設置表單控件值的格式是 email
minlength - 設置表單控件值的最小長度
maxlength - 設置表單控件值的最大長度
pattern - 設置表單控件的值需匹配 pattern 對應的模式
表單是幾乎每一個 Web 應用程序的一部分。雖然 Angular 爲咱們提供了幾個內置 validators (驗證器),但在實際工做中爲了知足項目需求,咱們常常須要爲應用添加一些自定義驗證功能。
詳細的內容能夠參考 - Angular 4.x 自定義驗證指令
在 Angular 建立自定義控件,須要考慮如下問題:
如何實現 model -> view 的數據綁定?
如何實現 view -> model 的數據同步?
若須要自定義驗證,應該如何實現?
如何向DOM元素添加有效性狀態,便於設置不一樣樣式?
如何讓控件能夠訪問 (accessible)?
該控件能應用於 template-driven 表單?
該控件能應用於 model-driven 表單?
詳細的內容能夠參考 - Angular 4.x 自定義表單控件
export declare enum ChangeDetectionStrategy { OnPush = 0, // 變化檢測器的狀態值是 CheckOnce Default = 1, // 組件默認值 - 變化檢測器的狀態值是 CheckAlways,即始終執行變化檢測 }
export declare enum ChangeDetectorStatus { CheckOnce = 0, // 表示在執行detectChanges以後,變化檢測器的狀態將會變成Checked Checked = 1, // 表示變化檢測將被跳過,直到變化檢測器的狀態恢復成CheckOnce CheckAlways = 2, // 表示在執行detectChanges以後,變化檢測器的狀態始終爲CheckAlways Detached = 3, // 表示該變化檢測器樹已從根變化檢測器樹中移除,變化檢測將會被跳過 Errored = 4, // 表示在執行變化檢測時出現異常 Destroyed = 5, // 表示變化檢測器已被銷燬 }
String -> String
UpperCasePipe
LowerCasePipe
TitleCasePipe
Number -> String
DecimalPipe
PercentPipe
CurrencyPipe
Object -> String
JsonPipe
DatePipe
Tools
SlicePipe
AsyncPipe
I18nPluralPipe
I18nSelectPipe
詳細的內容能夠參考 - Angular 2 Pipe
pure 管道:僅當管道輸入值變化的時候,才執行轉換操做,默認的類型是 pure 類型。(備註:輸入值變化是指原始數據類型如:string、number、boolean 等的數值或對象的引用值發生變化)。
impure 管道:在每次變化檢測期間都會執行,如鼠標點擊或移動都會執行 impure 管道。
自定義管道的步驟:
使用 @Pipe 裝飾器定義 Pipe 的 metadata 信息,如 Pipe 的名稱 - 即 name 屬性
實現 PipeTransform 接口中定義的 transform 方法
RepeatPipe 定義
import {Pipe, PipeTransform} from '@angular/core'; @Pipe({name: 'repeat'}) export class RepeatPipe implements PipeTransform { transform(value: any, times: number) { return value.repeat(times); } }
RepeatPipe 使用
<div> <p ngNonBindable>{{ 'lo' | repeat:3 }}</p> <p>{{ 'lo' | repeat:3 }}</p> <!-- Output: lololo --> </div>
詳細的內容能夠參考 - Angular 2 Pipe