11. 變化監測:Angular提供了數據綁定的功能.所謂的數據綁定就是將組件類的數據和頁面的DOM元素關聯起來.當數據發生變化時,Angular可以監測到這些變化,並對其所綁定的DOM元素
進行相應的更新,反之亦然.
異步事件的發生會致使組件中的數據變化,但Angular並非捕捉對象的變化,它採用的是在適當的時機去檢驗對象的值是被改動.這個時機是由NgZone這個服務去掌控的,它獲取到了整個
應用的執行上下文,可以對相關的異步事件發生,完成或者異常等進行捕獲,而後驅動Angular的變化監測機制執行.
11.1 數據變化的源頭:在應用程序當中,大體有這三種引發數據變化的應用場景.
1.用戶的行爲操做,即頁面操做所引起的用戶事件,如click,changes,hover等.
2.先後端的數據交互,如從後端服務拉取頁面所需的接口數據,如XMLHttpRequest/WebSocket等.
3.各種定時任務,即在某個延時後再來響應對應的操做.從而對頁面數據作出改變,如setTimeout,setInterval,requestAnimationFrame等.
以上三種可能致使數據變化的情景有一個共同的特徵,即它們都是異步的處理,使用異步回調函數句柄來處理相關數據操做.所以,任意一個異步操做,都有可能在數據層面上發生改變,
這回致使應用程序的狀態被改變.若是能夠在每個異步回調函數執行結束後,通知Angular內核進行變化監測,那麼任何數據的更改就能夠在視圖層實時額反饋出來.
11.2 變更通知機制:經過異步事件來通知Angular進行變化監測,讓任何數據的變動能夠被實時的反映出來.Angular自己不具有捕獲異步事件的機制,經過引入NgZone服務來實現.
NgZone是基於Zone來實現的,NgZone從Zone中fork了一份實例,是Zone派生出的一個子Zone,在Angular環境內註冊的異步事件都運行在這個子Zone上.
Zone是如何具有異步事件捕獲能力的?Zone以一樣的接口,不一樣的實現方式並替換了一系列與JavaScript的事件相關的標準方法.所以當開發者使用標準接口時,實際上會顯調用Zone
的替換方法,再由這些替換方法調用底層的標準方法.這種對上層應用透明的設計,使得在引入Zone的時候,原有代碼不須要作太大的改動.
NgZone擴展了一些API並添加了一些能夠被訂閱的自定義事件,這些自定義事件是Observable流:
1.onUnstable:在Angular單次事件啓動前,觸發消息通知訂閱器.
2.onMicrotaskEmpty:在Zone完成當前Angular單次事件任務時,當即通知訂閱者.
3.onStable:在完成onMicrotaskEmpty回調函數以後,在視圖變化以前當即通知訂閱者,經常使用來驗證應用程序的狀態.
因爲NgZone只是全局Zone的一個fork,Angular可以決定在Zone內需不須要執行變化監測,如NgZone的runOutsideAngular()方法可讓Angular不執行變化監測.
runOutsideAnglar():即通知NgZone的父Zone在捕獲到異步事件時直接返回,從而不在觸發自定義的onMicrotaskEmpty事件,直接做用就是不在通知Angular執行變化監測.
針對上面的說明,對變更通知機制可做詳細闡述,如:當有異步事件觸發致使數據變化時,這些異步事件會被Zones捕獲並觸發onUnstable自定義事件,在該自定義事件綁定的函數中
來通知Angular去執行變化監測,如當鼠標經的mousemove事件發生時,它將觸發變化通知監測.
11.3 變化監測的處理機制:Angular應用由大大小小的組件組成,這些有相互依賴關係的組件組成了一個線性的組件樹.此外,沒個組件都有一個本身的變化檢測器,由此組成的變化檢測樹.
變化監測樹的數據是由上到下單向流動,變化監測的執行老是從根組件開始,從上到下的監測每個組件的變化.
當一個異步事件發生並致使其中組件數據的改變,在組件中綁定的相關處理事件將會被觸發,事件句柄(對象)處理完成相關邏輯以後,NgZone將會執行對應的鉤子函數並通知Angular
去執行一次變化監測.
默認狀況下,任何一個組件模型中的數據變化都會致使整個組件樹的變化監測,可是有不少組件的輸入屬性是沒有變化的,所以沒有必要對這些組件進行變化監測操做.減小沒必要要的監測
操做能夠提高應用程序的性能.
11.4 變化監測類:Angular在整個運行期間都會爲每一個組件建立變化檢測類的實例,該實例提供了相關的方法來手動管理變化監測.因爲Angular並不知道是哪一個組件發生了變化,可是開發者
知道,因此能夠給這個組件作一個標記,以此來通知Angular僅僅監測這個組件所在路徑上的組件便可.
變化監測類ChangeDelectorRef提供的主要接口以下:
1.markForCheck():把根組件到該組件之間的這條路徑標記起來,通知Angular在下次觸發變化監測時必須檢查這條路徑上的組件.
2.detach():從變化監測樹中分離變化監測器,該組件的變化監測器將不在執行變化監測,除非再次手動執行reattact().
3.reattact():把分離的變化監測器從新安裝上,使得該組件機器子組件都能執行變化檢測.
4.detectChanges():手動觸發執行該組件到各個子組件的一次變化監測.
示例以下:
@Component({
selector:'list',
template:`
<ul>
<li *ngFor="let contact of contacts">
<list-item [contact]="contact"></list-item>
</li>
</ul>
`
})
export class ListComponent implements OnInit{
contacts:any={};css
//構造器的參數用了語法糖,能夠快捷建立一個屬cd並綁定到構造函數參數上.
contructor(private cd:ChangeDetectorRef){
//首先將該組件(包含其子組件)從變化監測樹中排除出去
cd.detach();
//定時手動執行變化監測,即:每一個5秒手動觸發一次該組件及其子組件的Angular變化監測
setInterval(()=>{ //這裏簡化了contacts數據來源代碼
this.cd.detectChanges();
},5000);
}
ngOnInit(){
this.getContacts();
}
getContacts(){
this.contacts=data; //這裏簡化了contacts數據來源代碼
}
}
11.4 變化監測策略:@Component中有個可選的元數據changeDetection,它的做用是讓開發者定義每一個組件的變化監視策略.在使用該功能前,須要先導入ChangeDetectionStrategy
對象.ChangeDetectionStrategy枚舉類型值有兩種:
1.Default:組件的每次變化監測都會檢查其內部全部數據(引用對象會被深度遍歷),以此得出數據先後變化;
2.OnPush:組件的變化監測只檢查輸入屬性(@Input修飾的變量)的值是否發生改變,當這個值是引用類型(如Object,Array等)時,則僅對比引用是否改變.
OnPush策略相比Default下降了變化監測的複雜度,對性能提高更好,可是OnPush策略只對比值的'引用',這在某些場景中可能會得不到預期的效果,若是但願組件也能正常更新數
據,解決的辦法有兩個:
1.使用Default策略,但會犧牲性能.xzm
2.使用Immutable對象來傳值,這是比較推薦的作法.
使用Immutable對象能夠確保當對象值的引用地址不變時,對象內部的值或結構也會保持不變.反之,當對象內部發生變化時,對象的引用必然發生改變.
示例以下:
//子組件代碼
import { Component,Input,ChangeDetectionStragety } from '@angular/core';
@Component({
selector:'list-item',
template:`
<div>
<label> {{contact.get('name')}} </lable>
<span>{{ contact.get('telNum') }}</span>
</div>
`,
changeDetection:ChangeDetectionStragety.OnPush
})
export class ListItemComponet{
@Input() contact:any={};
//...
}
//父組件代碼
import { Component } from '@angular/core';
import Immutable from 'immutable';html
@Component({
//...
template:`
<list-item [contact]="contactItem"></list-item>
<button (click)="doUpdate()">更新</button>
`,
changeDetection:ChangeDetectionStragety.OnPush
})
export classListComponent{
contactItem:any;
constructor(){
this.contactItem=Immutable.map({
name:'xzm',
telNum:'12345678'
});
}
}webpack
12 組件的其餘元數據:
12.1 host:是個功能強大的元數據,主要是用在指令中.經過host指令,能夠指定此指令/組件的事件,動做和屬性等.
12.2 exportAs:主要是在指令中使用,做用是將指令分配給一個變量,至關於別名.
12.3 moduleId:包含該組件模塊的id,它被用於解析模板和樣式的相對路徑.在Dart中它能夠被自動肯定,在CommonJS中,它老是被設置爲module.id,這中狀況下,若是CSS,HTML,TypeScript
文件在同一目錄,如app下,則能夠去除基準路徑,如"app/".
示例:
@Component({
moduleId:module.id,
templateUrl:'some.component.html',
styleUrls:['some.component.css']
})
選擇Webpack方案,能夠採用"./"開頭的相對路徑寫法,webpack會自動調用require()方法來加載這些模板與樣式.
12.4 queries:設置須要被注入到組件的查詢.在組件中主要有兩種查詢,即視圖查詢和內容查詢,它們分別會在ngAfterViewInit和ngAfterContentInit回調函數被調用以前設置.
1.視圖查詢示例:
//包裝一個輸入框成組件,實現一單被渲染,則獲取焦點.
@Component({
selector:'my-input',
template:`
<input #theInput type="text" />
`,
queries:{
input:new ViewChild('theInput')
}
})
export class MyInput implements AfterViewInit{
input : ElemenetRef=null;web
constructor(private renderer:Renderer){}後端
ngAfterViewInit(){
this.renderer.invokeElementMethod(this.input.nativeElement,'focus');
}
}
以上代碼其實至關於以下代碼:
@Component({
selector:'my-input',
template:`
<input #theInput type="text" />
`
})
export class MyInput implements AfterViewInit{
@ViewChild('theInput') input:ElemenetRef;app
constructor(private renderer:Renderer){}異步
ngAfterViewInit(){
this.renderer.invokeElementMethod(this.input.nativeElement,'focus');
}
}
2.內容查詢:和視圖查詢相似,不過內容查詢是配和ng-content使用的.
示例:
<my-list>
<li *ngFor="#item of items">{{item}}</li>
</my-list>ide
@Directive({selector:'li'})
export class ListItem{}函數
@Component({
selector:'my-list',
template:`
<input #theInput type="text" />
`,
queries:{
items:new ContentChildrenn(ListItem) //經過ListItem的選擇器綁定li元素
}
})
export class MyInput implements AfterContentInit{
items:new QueryList<ListItem></ListItem>;性能
constructor(private renderer:Renderer){}
ngAfterContentInit(){
}
}
12.5 animations:animations元數據提供了便捷的動畫定義方法,使用方式就和自定義標籤同樣.animations元數據定義須要先從@angular/core引入一些用於動畫的函數,
以下:import {
trigger,
state,
style,
transition,
animate
} from '@angular/core';
定義一個按鈕狀態動畫效果,有"on"和"off"兩種狀態,默認是"on",點擊按鈕切換狀態,顏色變紅,字變小(1.2~1).
示例代碼以下:
//...
animations:[
trigger("buttonStatus",[
state('on',style( { color:'#of2', transform:'scale(1.2)' } )),
state('off',style( { color:'f00',transform:'scale(1)' } )),
transition('off=>on',animate('100ms ease-in')),
transition('on=>off',animate('100ms ease-out'))
])
]
有了以上定義的動畫效果,就能夠在組件模板上經過@triggerName的方式來應用到元素當中,如:
//...
template:`
<div>
<button @buttonStatus="status" (click)="toggleStatus">{{status}}</button>
</div>
`
//...
export class example {
status:string='on';
toggleStatus(){
this.status=(this.status==='on') ? 'off' : 'on';
}
}