https://blog.angularindepth.com/exploring-angular-dom-abstractions-80b3ebcfc02javascript
原文連接,牆裂推薦閱讀原文,事實上這篇文章網上已有的翻譯都有或多或少的錯誤,我這篇確定也不例外。html
Angular文檔中關於使用Angular DOM的操做,老是會提到一個或幾個類: ElementRef, TemplateRef, ViewContainerRef等。本文旨在描述這種模型。java
在Angular中DOM被抽象出ElementRef
, TemplateRef
, ViewRef
, ComponentRef
和ViewContainerRef
。web
@ViewChild
| @ViewChildren
在深刻這些DOM抽象以前,咱們先了解一下如何在組件/指令類中訪問這些DOM抽象。Angular提供了一個DOM查詢機制。這個機制由@ViewChild
和@ViewChildren
兩個裝飾器完成。他倆的使用方法是一毛同樣的,只是返回結果有所不一樣,顯而易見,後折返回一個列表,前者只返回一個引用。typescript
一般狀況下,這些裝飾器都和模板引用標識(template reference variable)
同時出現,模板引用標識(template reference variable)
是一個在模板文件裏給dom元素命名的東西,你也能夠把它理解成相似於dom元素的id的存在。給一個元素增長一個引用標識,你就能夠經過這兩個裝飾器來獲取它們。瀏覽器
@Component({
selector: 'sample',
template: ` <span #tref>I am span</span> `
})
export class SampleComponent implements AfterViewInit {
@ViewChild("tref", {read: ElementRef}) tref: ElementRef;
ngAfterViewInit(): void {
// outputs `I am span`
console.log(this.tref.nativeElement.textContent);
}
}
複製代碼
@ViewChild的基本語法爲@ViewChild([reference from template], {read: [reference type]});
第二個參數並非必須的,假如他是一個簡單的dom元素,相似span這樣的,angular會推斷爲ElementRef。若是是一個template元素,則會推斷爲TemplateRef。固然,也有一些類型好比ViewContainerRef是不能被推斷出來的,須要手動聲明在read的值中,Others, like ViewRef cannot be returned from the DOM and have to be constructed manually.安全
ok,如今咱們知道怎麼執行dom查詢了,咱們來開始深刻這些dom抽象吧~數據結構
ElementRef能夠說是墜基本的dom抽象了。app
class ElementRef<T> {
constructor(nativeElement: T)
nativeElement: T
}
複製代碼
這個類中只包含了與之關聯的原生元素,經過它你能夠很輕鬆的查找到dom元素。dom
console.log(this.tref.nativeElement.textContent);
可是Angular並不推薦這種直接dom元素進行操做的方法,不光是安全緣由,更是由於這樣作會使得一套代碼多平臺運行的原則受到了打破,它使得應用和渲染層緊密耦合。我嚼的這並非由於使用nativeElement
致使的,而是由於使用了textContent
這樣的DOM API致使的。事實上Angular所實現的DOM操做模型幾乎沒有用到這麼底層的dom訪問。
對任何dom元素使用@ViewChild
裝飾器都能返回ElementRef。由於全部的組件其實都是被host在普通的DOM元素上,全部的指令都是做用於DOM元素,因此藉助於Angular的依賴注入機制,全部的組件/指令類能夠獲取它們的**宿主元素(host element)**的ElementRef。方法以下:
@Component({
selector: 'sample',
...
})
export class SampleComponent{
constructor(private hostElement: ElementRef) {
//outputs <sample>...</sample>
console.log(this.hostElement.nativeElement.outerHTML);
}
}
複製代碼
因此既然一個組件能夠經過DI機制輕鬆的獲取它的宿主元素,@ViewChild裝飾器通常就用來獲取模板中的一個子元素了。對指令而言則是徹底相反的,它們沒有視圖、沒有子元素,因此它們一般與他們所依附的元素一塊兒工做。
模板的概念對於廣大web開發者而言能夠說是見的多了。它是在應用中能被屢次複用的一個DOM元素的集合。在HTML5將template便籤列入標準以前,多數模板都是經過script標籤包裹的方式實現的。這種實現方式不是本文的重點,因此咱們按住不表。
咱們來說講template標籤,做爲HTML5新增長的標籤,瀏覽器會解析這個標籤並生成對應的DOM元素,可是並不會直接渲染到頁面上。經過template元素的content屬性咱們就能夠獲取到這個DOM元素。
<script> let tpl = document.querySelector('#tpl'); let container = document.querySelector('.insert-after-me'); insertAfter(container, tpl.content); </script>
<div class="insert-after-me"></div>
<ng-template id="tpl">
<span>I am span in template</span>
</ng-template>
複製代碼
Angular擁抱了這種實現方式,並聲明瞭TemplateRef類來與**模板(template)**一塊兒工做。使用方法以下:
@Component({
selector: 'sample',
template: ` <ng-template #tpl> <span>I am span in template</span> </ng-template> `
})
export class SampleComponent implements AfterViewInit {
@ViewChild("tpl") tpl: TemplateRef<any>;
ngAfterViewInit() {
let elementRef = this.tpl.elementRef;
// outputs `template bindings={}`
console.log(elementRef.nativeElement.textContent);
}
}
複製代碼
Angular在渲染過程當中移除了template標籤。並在其位置插入了一條註釋,
<sample>
<!--template bindings={}-->
</sample>
複製代碼
下面是Angular文檔中對TemplateRef類的描述 TemplateRef類的數據結構以下:
class TemplateRef<C> {
get elementRef: ElementRef
createEmbeddedView(context: C): EmbeddedViewRef<C>
}
複製代碼
The location in the View where the Embedded View logically belongs to.
他有一個屬性elementRef: ElementRef,表明了這個內嵌視圖在view中所屬的位置,也就是ng-template
標籤上的模板引用標識獲取的ElementRef。
The data-binding and injection contexts of Embedded Views created from this TemplateRef inherit from the contexts of this location.
Typically new Embedded Views are attached to the View Container of this location, but in advanced use-cases, the View can be attached to a different container while keeping the data-binding and injection context from the original location.
他有一個方法createEmbeddedView()
能夠建立一個內嵌視圖並將其駐留在一個視圖容器上,同時還能返回一個對視圖的引用:ViewRef。
ViewRef正是對Angular中最基本的UI構成-View的抽象。他是一系列元素的最小集合,被同時建立出來又被同時銷燬。Angular高度鼓勵開發者們將UI視爲一系列View的組成,而不是一個個html標籤組成的樹。
Angular有且僅有兩種視圖類型:內嵌視圖(Embedded Views)和宿主視圖(Host Views)。一般狀況下,內嵌視圖每每跟Template相關聯,而宿主視圖與組件相關聯。
TemplateRef中的方法createEmbeddedView()
方法能夠直接建立出一個內嵌視圖,該方法返回的類型正是ViewRef;
ngAfterViewInit() {
let view = this.tpl.createEmbeddedView(null);
}
複製代碼
當一個組件被動態生成時,宿主視圖也就隨之被建立出來了。經過ComponentFactoryResolver
類你能夠輕鬆的動態建立出一個組件。
constructor( private _injector: Injector, private _r: ComponentFactoryResolver, ) {
let factory = this._r.resolveComponentFactory(aComponent);
let componentRef = factory.create(this._injector);
let view = componentRef.hostView;
}
複製代碼
這裏薛薇的解釋一下以上代碼,在Angular中每一個組件都和一個**注入器(Injector)**的實例綁定的,所以當咱們動態建立一個組件的時候,咱們會把當前的注入器實例傳遞進create()方法中。固然,除了上述代碼,若是你想要獲取一個組件的組件工廠,你須要在當前模塊的entryComponents中聲明這個組件。
下面是Angular官網中對VIewRef類的一點描述:
class ViewRef extends ChangeDetectorRef {
get destroyed: boolean
destroy(): void
onDestroy(callback: Function): any
// 自ChangeDetectorRef繼承來的屬性方法
markForCheck(): void
detach(): void
detectChanges(): void
checkNoChanges(): void
reattach(): void
}
複製代碼
好的,如今咱們知道了如何建立內嵌視圖和宿主視圖。當這些視圖建立完畢以後咱們就能夠經過ViewContainer將他們插入到DOM中。
首先要聲明的是,任何DOM元素均可以被用做爲視圖容器。英催思挺的是Angular不會在元素內部插入視圖,而是把這些視圖添加到綁定到ViewContainer的元素後面。這跟router-outlet
插入組件的方法灰常類似。
一般狀況下,一個墜佳的建立ViewContainer的地方是<ng-container></ng-container>
標籤。最終它會被渲染爲一行註釋而不會在dom中引入冗餘的元素。
@Component({
selector: 'sample',
template: ` <span>I am first span</span> <ng-container #vc></ng-container> <span>I am last span</span> `
})
export class SampleComponent implements AfterViewInit {
@ViewChild("vc", {read: ViewContainerRef}) vc: ViewContainerRef;
ngAfterViewInit(): void {
// outputs one line of comment
console.log(this.vc.element.nativeElement.textContent);
}
}
複製代碼
Angular文檔中對於ViewContainerRef類的描述以下:
class ViewContainerRef {
get element: ElementRef
get injector: Injector
get parentInjector: Injector
get length: number
clear(): void
get(index: number): ViewRef | null
createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, index?: number): EmbeddedViewRef<C>
createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector, projectableNodes?: any[][], ngModule?: NgModuleRef<any>): ComponentRef<C>
insert(viewRef: ViewRef, index?: number): ViewRef
move(viewRef: ViewRef, currentIndex: number): ViewRef
indexOf(viewRef: ViewRef): number
remove(index?: number): void
detach(index?: number): ViewRef | null
}
複製代碼
前面咱們已經知道了兩種視圖類型是如何從template和component建立出來的,一旦咱們有了view,就能夠經過viewContainer的insert()方法將其插入到dom中。
import {
AfterViewInit,
Component,
TemplateRef,
ViewChild,
ViewContainerRef
} from '@angular/core';
@Component({
selector: 'app-root',
template: ` <span>firsr para</span> <ng-container #vc></ng-container> <span>second para</span> <ng-template #tpl> <span>the para in template</span> </ng-template> `
})
export class AppComponent implements AfterViewInit {
@ViewChild('vc', {read: ViewContainerRef}) viewContainer: ViewContainerRef;
@ViewChild('tpl') tpl: TemplateRef<any>;
ngAfterViewInit() {
const tp_view = this.tpl.createEmbeddedView(null);
this.viewContainer.insert(tp_view);
set
}
}
// 輸出
// <span>firsr para</span>
// <!---->
// <span>the para in template</span>
// <span>second para</span>
// <!---->
複製代碼
要移除這個被插入的元素,只要調用viewContainer的detach()方法。全部其餘方法都是自解釋性的,可用於獲取索引視圖的引用,將視圖移到另外一個位置,或者從容器中刪除全部視圖。
createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, index?: number): EmbeddedViewRef<C>
createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector,
複製代碼
這兩個方法至關於對咱們上面的代碼進行了一層封裝,他會從template或component中建立一個view出來並插入到dom中相應的位置。
如今看起來彷佛有不少概念須要去理解吸取。可是實際上這些概念的調條理都十分清晰,而且這些概念組成了一個十分清晰的視圖操做DOM的模型。
經過@ViewChild和模板引用標識符你能夠獲取到Angular DOM抽象的引用。圍繞DOM元素最簡單的包裹是ElementRef。
對於模板(template)而言,你能夠經過TemplateRef來建立一個內嵌視圖(Embedded View);宿主視圖則能夠經過ComponentFactoryResolver建立的componentRef來獲取。
經過ViewContainerRef咱們則能夠操做這些視圖
結束!哈!