摘要:在探索抽象類(lèi)前,先了解下如何在組件指令中獲取這些抽象類(lèi)。下面示例描述在組建模板中如何創(chuàng)建如同其他抽象類(lèi)一樣,通過(guò)屬性綁定元素,比如上例中,綁定的是會(huì)被渲染為注釋的元素,所以輸出也將是。你可以使用查詢(xún)模板引用變量來(lái)獲得抽象類(lèi)。
原文鏈接:Exploring Angular DOM manipulation techniques using ViewContainerRef如果想深入學(xué)習(xí) Angular 如何使用 Renderer 和 View Containers 技術(shù)操作 DOM,可以查閱 YouTube 視頻 my talk at NgVikings。
每次我讀到 Angular 如何操作 DOM 相關(guān)文章時(shí),總會(huì)發(fā)現(xiàn)這些文章提到 ElementRef、TemplateRef、ViewContainerRef 和其他的類(lèi)。盡管這些類(lèi)在 Angular 官方文檔或相關(guān)文章會(huì)有涉及,但是很少會(huì)去描述整體思路,這些類(lèi)如何一起作用的相關(guān)示例也很少,而本文就主要描述這些內(nèi)容。
如果你來(lái)自于 angular.js 世界,很容易明白如何使用 angular.js 操作 DOM。angular.js 會(huì)在 link 函數(shù)中注入 DOM element,你可以在組件模板里查詢(xún)?nèi)魏喂?jié)點(diǎn)(node),添加或刪除節(jié)點(diǎn)(node),修改樣式(styles),等等。然而這種方式有個(gè)主要缺陷:與瀏覽器平臺(tái)緊耦合。
新版本 Angular 需要在不同平臺(tái)上運(yùn)行,如 Browser 平臺(tái),Mobile 平臺(tái)或者 Web Worker 平臺(tái),所以,就需要在特定平臺(tái)的 API 和框架接口之間進(jìn)行一層抽象(abstraction)。Angular 中的這層抽象就包括這些引用類(lèi)型:ElementRef、TemplateRef、ViewRef、ComponentRef 和 ViewContainerRef。本文將詳細(xì)講解每一個(gè)引用類(lèi)型(reference type)和該引用類(lèi)型如何操作 DOM。
@ViewChild在探索 DOM 抽象類(lèi)前,先了解下如何在組件/指令中獲取這些抽象類(lèi)。Angular 提供了一種叫做 DOM Query 的技術(shù),主要來(lái)源于 @ViewChild 和 @ViewChildren 裝飾器(decorators)。兩者基本功能相同,唯一區(qū)別是 @ViewChild 返回單個(gè)引用,@ViewChildren 返回由 QueryList 對(duì)象包裝好的多個(gè)引用。本文示例中主要以 ViewChild 為例,并且描述時(shí)省略 @。
通常這兩個(gè)裝飾器與模板引用變量(template reference variable)一起使用,模板引用變量?jī)H僅是對(duì)模板(template)內(nèi) DOM 元素命名式引用(a named reference),類(lèi)似于 html 元素的 id 屬性。你可以使用模板引用(template reference)來(lái)標(biāo)記一個(gè) DOM 元素,并在組件/指令類(lèi)中使用 ViewChild 裝飾器查詢(xún)(query)它,比如:
@Component({
selector: "sample",
template: `
I am 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 裝飾器基本語(yǔ)法是:
@ViewChild([reference from template], {read: [reference type]});
上例中你可以看到,我把 tref 作為模板引用名稱(chēng),并將 ElementRef 與該元素聯(lián)系起來(lái)。第二個(gè)參數(shù) read 是可選的,因?yàn)?Angular 會(huì)根據(jù) DOM 元素的類(lèi)型推斷出該引用類(lèi)型。例如,如果它(#tref)掛載的是類(lèi)似 span 的簡(jiǎn)單 html 元素,Angular 返回 ElementRef;如果它掛載的是 template 元素,Angular 返回 TemplateRef。一些引用類(lèi)型如 ViewContainerRef 就不可以被 Angular 推斷出來(lái),所以必須在 read 參數(shù)中顯式申明。其他的如 ViewRef 不可以掛載在 DOM 元素中,所以必須手動(dòng)在構(gòu)造函數(shù)中編碼構(gòu)造出來(lái)。
現(xiàn)在,讓我們看看應(yīng)該如何獲取這些引用,一起去探索吧。
ElementRef這是最基本的抽象類(lèi),如果你查看它的類(lèi)結(jié)構(gòu),就發(fā)現(xiàn)它只包含所掛載的元素對(duì)象,這對(duì)訪問(wèn)原生 DOM 元素很有用,比如:
// outputs `I am span` console.log(this.tref.nativeElement.textContent);
然而,Angular 團(tuán)隊(duì)不鼓勵(lì)這種寫(xiě)法,不但因?yàn)檫@種方式會(huì)暴露安全風(fēng)險(xiǎn),而且還會(huì)讓你的程序與渲染層(rendering layers)緊耦合,這樣就很難在多平臺(tái)運(yùn)行你的程序。我認(rèn)為這個(gè)問(wèn)題并不是使用 nativeElement 而是使用特定的 DOM API 造成的,如 textContent。但是后文你會(huì)看到,Angular 實(shí)現(xiàn)了操作 DOM 的整體思路模型,這樣就不再需要低階 API,如 textContent。
使用 ViewChild裝飾的 DOM 元素會(huì)返回 ElementRef,但是由于所有組件掛載于自定義 DOM 元素,所有指令作用于 DOM 元素,所以組件和指令都可以通過(guò) DI(Dependency Injection)獲取宿主元素的ElementRef 對(duì)象。比如:
@Component({ selector: "sample", ... export class SampleComponent{ constructor(private hostElement: ElementRef) { //outputs... console.log(this.hostElement.nativeElement.outerHTML); } ...
所以組件通過(guò) DI(Dependency Injection)可以訪問(wèn)到它的宿主元素,但 ViewChild 裝飾器經(jīng)常被用來(lái)獲取模板視圖中的 DOM 元素。然而指令卻相反,因?yàn)橹噶顩](méi)有視圖模板,所以主要用來(lái)獲取指令掛載的宿主元素。
TemplateRef對(duì)于大部分開(kāi)發(fā)者來(lái)說(shuō),模板概念很熟悉,就是跨程序視圖內(nèi)一堆 DOM 元素的組合。在 HTML5 引入 template 標(biāo)簽前,瀏覽器通過(guò)在 script 標(biāo)簽內(nèi)設(shè)置 type 屬性來(lái)引入模板,比如:
這種方式不僅有語(yǔ)義缺陷,還需要手動(dòng)創(chuàng)建 DOM 模型,然而通過(guò) template 標(biāo)簽,瀏覽器可以解析 html 并創(chuàng)建 DOM 樹(shù),但不會(huì)渲染它,該 DOM 樹(shù)可以通過(guò) content 屬性訪問(wèn),比如:
I am span in template
Angular 采用 template 標(biāo)簽這種方式,實(shí)現(xiàn)了 TemplateRef 抽象類(lèi)來(lái)和 template 標(biāo)簽一起合作,看看它是如何使用的(譯者注:ng-template 是 Angular 提供的類(lèi)似于 template 原生 html 標(biāo)簽):
@Component({ selector: "sample", template: `I am span in template ` }) export class SampleComponent implements AfterViewInit { @ViewChild("tpl") tpl: TemplateRef; ngAfterViewInit() { let elementRef = this.tpl.elementRef; // outputs `template bindings={}` console.log(elementRef.nativeElement.textContent); } }
Angular 框架從 DOM 中移除 template 元素,并在其位置插入注釋?zhuān)@是渲染后的樣子:
TemplateRef 是一個(gè)結(jié)構(gòu)簡(jiǎn)單的抽象類(lèi),它的 elementRef 屬性是對(duì)其宿主元素的引用,還有一個(gè) createEmbeddedView 方法。然而 createEmbeddedView 方法很有用,因?yàn)樗梢詣?chuàng)建一個(gè)視圖(view)并返回該視圖的引用對(duì)象 ViewRef。
ViewRef該抽象表示一個(gè) Angular 視圖(View),在 Angular 世界里,視圖(View)是一堆元素的組合,一起被創(chuàng)建和銷(xiāo)毀,是構(gòu)建程序 UI 的基石。Angular 鼓勵(lì)開(kāi)發(fā)者把 UI 作為一堆視圖(View)的組合,而不僅僅是 html 標(biāo)簽組成的樹(shù)。
Angular 支持兩種類(lèi)型視圖:
嵌入視圖(Embedded View),由 Template 提供
宿主視圖(Host View),由 Component 提供
創(chuàng)建嵌入視圖模板僅僅是視圖的藍(lán)圖,可以通過(guò)之前提到的 createEmbeddedView 方法創(chuàng)建視圖,比如:
ngAfterViewInit() { let view = this.tpl.createEmbeddedView(null); }創(chuàng)建宿主視圖
宿主視圖是在組件動(dòng)態(tài)實(shí)例化時(shí)創(chuàng)建的,一個(gè)動(dòng)態(tài)組件(dynamic component)可以通過(guò) ComponentFactoryResolver 創(chuàng)建:
constructor(private injector: Injector, private r: ComponentFactoryResolver) { let factory = this.r.resolveComponentFactory(ColorComponent); let componentRef = factory.create(injector); let view = componentRef.hostView; }
在 Angular 中,每一個(gè)組件綁定著一個(gè)注入器(Injector)實(shí)例,所以創(chuàng)建 ColorComponent 組件時(shí)傳入當(dāng)前組件(即 SampleComponent)的注入器。另外,別忘了,動(dòng)態(tài)創(chuàng)建組件時(shí)需要在模塊(module)或宿主組件的 EntryComponents 屬性添加被創(chuàng)建的組件。
現(xiàn)在,我們已經(jīng)看到嵌入視圖和宿主視圖是如何被創(chuàng)建的,一旦視圖被創(chuàng)建,它就可以使用 ViewContainer 插入 DOM 樹(shù)中。下文主要探索這個(gè)功能。
ViewContainerRef視圖容器就是掛載一個(gè)或多個(gè)視圖的容器。
首先需要說(shuō)的是,任何 DOM 元素都可以作為視圖容器,然而有趣的是,對(duì)于綁定 ViewContainer 的 DOM 元素,Angular 不會(huì)把視圖插入該元素的內(nèi)部,而是追加到該元素后面,這類(lèi)似于 router-outlet 插入組件的方式。
通常,比較好的方式是把 ViewContainer 綁定在 ng-container 元素上,因?yàn)?ng-container 元素會(huì)被渲染為注釋?zhuān)瑥亩粫?huì)在 DOM 中引入多余的 html 元素。下面示例描述在組建模板中如何創(chuàng)建 ViewContainer:
@Component({ selector: "sample", template: ` I am first spanI am last span ` }) export class SampleComponent implements AfterViewInit { @ViewChild("vc", {read: ViewContainerRef}) vc: ViewContainerRef; ngAfterViewInit(): void { // outputs `template bindings={}` console.log(this.vc.element.nativeElement.textContent); } }
如同其他抽象類(lèi)一樣,ViewContainer 通過(guò) element 屬性綁定 DOM 元素,比如上例中,綁定的是 會(huì)被渲染為注釋的 ng-container 元素,所以輸出也將是 template bindings={}。
操作視圖ViewContainer 提供了一些操作視圖 API:
class ViewContainerRef { ... clear() : void insert(viewRef: ViewRef, index?: number) : ViewRef get(index: number) : ViewRef indexOf(viewRef: ViewRef) : number detach(index?: number) : ViewRef move(viewRef: ViewRef, currentIndex: number) : ViewRef }
從上文我們已經(jīng)知道如何通過(guò)模板和組件創(chuàng)建兩種類(lèi)型視圖,即嵌入視圖和組件視圖。一旦有了視圖,就可以通過(guò) insert 方法插入 DOM 中。下面示例描述如何通過(guò)模板創(chuàng)建嵌入視圖,并在 ng-container 標(biāo)記的地方插入該視圖(譯者注:從上文中知道是追加到ng-container后面,而不是插入到該 DOM 元素內(nèi)部)。
@Component({ selector: "sample", template: ` I am first spanI am last span I am span in template ` }) export class SampleComponent implements AfterViewInit { @ViewChild("vc", {read: ViewContainerRef}) vc: ViewContainerRef; @ViewChild("tpl") tpl: TemplateRef; ngAfterViewInit() { let view = this.tpl.createEmbeddedView(null); this.vc.insert(view); } }
通過(guò)上面的實(shí)現(xiàn),最后的 html 看起來(lái)是:
I am first span I am span in template I am last span
可以通過(guò) detach 方法從視圖中移除 DOM,其他的方法可以通過(guò)方法名知道其含義,如通過(guò)索引獲取視圖引用對(duì)象,移動(dòng)視圖位置,或者從視圖容器中移除所有視圖。
創(chuàng)建視圖ViewContainer 也提供了手動(dòng)創(chuàng)建視圖 API :
class ViewContainerRef { element: ElementRef length: number createComponent(componentFactory...): ComponentRefcreateEmbeddedView(templateRef...): EmbeddedViewRef ... }
上面兩個(gè)方法是個(gè)很好的封裝,可以傳入模板引用對(duì)象或組件工廠對(duì)象來(lái)創(chuàng)建視圖,并將該視圖插入視圖容器中特定位置。
ngTemplateOutlet 和 ngComponentOutlet盡管知道 Angular 操作 DOM 的內(nèi)部機(jī)制是好事,但是要是有某種快捷方式就更好了啊。沒(méi)錯(cuò),Angular 提供了兩種快捷指令:ngTemplateOutlet 和 ngComponentOutlet。寫(xiě)作本文時(shí)這兩個(gè)指令都是實(shí)驗(yàn)性的,ngComponentOutlet 也將在版本 4 中可用(譯者注:現(xiàn)在版本 5.* 也是實(shí)驗(yàn)性的,也都可用)。如果你讀完了上文,就很容易知道這兩個(gè)指令是做什么的。
ngTemplateOutlet該指令會(huì)把 DOM 元素標(biāo)記為 ViewContainer,并插入由模板創(chuàng)建的嵌入視圖,從而不需要在組件類(lèi)中顯式創(chuàng)建該嵌入視圖。這樣,上面實(shí)例中,針對(duì)創(chuàng)建嵌入視圖并插入 #vc DOM 元素的代碼就可以重寫(xiě):
@Component({ selector: "sample", template: ` I am first spanI am last span I am span in template ` }) export class SampleComponent {}
從上面示例看到我們不需要在組件類(lèi)中寫(xiě)任何實(shí)例化視圖的代碼。非常方便,對(duì)不對(duì)。
ngComponentOutlet這個(gè)指令與 ngTemplateOutlet 很相似,區(qū)別是 ngComponentOutlet 創(chuàng)建的是由組件實(shí)例化生成的宿主視圖,不是嵌入視圖。你可以這么使用:
總結(jié)
看似有很多新知識(shí)需要消化啊,但實(shí)際上 Angular 通過(guò)視圖操作 DOM 的思路模型是很清晰和連貫的。你可以使用 ViewChild 查詢(xún)模板引用變量來(lái)獲得 Angular DOM 抽象類(lèi)。DOM 元素的最簡(jiǎn)單封裝是 ElementRef;而對(duì)于模板,你可以使用 TemplateRef 來(lái)創(chuàng)建嵌入視圖;而對(duì)于組件,可以使用 ComponentRef 來(lái)創(chuàng)建宿主視圖,同時(shí)又可以使用 ComponentFactoryResolver 創(chuàng)建 ComponentRef。這兩個(gè)創(chuàng)建的視圖(即嵌入視圖和宿主視圖)又會(huì)被 ViewContainerRef 管理。最后,Angular 又提供了兩個(gè)快捷指令自動(dòng)化這個(gè)過(guò)程:ngTemplateOutlet 指令使用模板創(chuàng)建嵌入視圖;ngComponentOutlet 使用動(dòng)態(tài)組件創(chuàng)建宿主視圖。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/93544.html
摘要:第一種方式是使用模塊加載器,如果你使用加載器的話,路由在加載子路由模塊時(shí)也是用的作為模塊加載器。還需注意的是,想要使用還需像這樣去注冊(cè)它你當(dāng)然可以在里使用任何標(biāo)識(shí),不過(guò)路由模塊使用標(biāo)識(shí),所以最好也使用相同。 原文鏈接:Here is what you need to know about dynamic components in?Angular showImg(https://se...
摘要:注意本文不是關(guān)于如何用編程的方式來(lái)創(chuàng)建組件的文章。在這個(gè)例子中,容器元素就是元素,模版將作為這個(gè)元素的兄弟節(jié)點(diǎn)被插入。用來(lái)演示以組件自身作為視圖容器,將組件中的模版插入視圖容器的效果。 原文鏈接:https://netbasal.com/angular-...作者:Netanel Basal譯者:而井 showImg(https://segmentfault.com/img/bVbl...
摘要:這些依賴(lài)對(duì)象也進(jìn)一步暴露了其設(shè)計(jì)思想。關(guān)鍵功能包括在上下文內(nèi)掛載在上下文外掛載在上下文外共享數(shù)據(jù)。在構(gòu)造必須依賴(lài),所以可以直接創(chuàng)建嵌入視圖,然后手動(dòng)強(qiáng)制執(zhí)行變更檢測(cè)。提供了兩個(gè)指令和。 @angular/material 是 Angular 官方根據(jù) Material Design 設(shè)計(jì)語(yǔ)言提供的 UI 庫(kù),開(kāi)發(fā)人員在開(kāi)發(fā) UI 庫(kù)時(shí)發(fā)現(xiàn)很多 UI 組件有著共同的邏輯,所以他們把這些共...
摘要:翻譯在中操作意料之外的結(jié)果及優(yōu)化技術(shù)原文鏈接作者譯者而井我最近在的一個(gè)研討會(huì)上討論了中的高級(jí)操作的話題。首先,我會(huì)介紹在中操作的工具和方法,然后再介紹一些我在研討會(huì)上沒(méi)有說(shuō)過(guò)的更高級(jí)的優(yōu)化技術(shù)。 【翻譯】在Angular中操作DOM:意料之外的結(jié)果及優(yōu)化技術(shù) 原文鏈接:https://blog.angularindepth.c... 作者:Max Koretskyi 譯者:而井 ...
摘要:對(duì)此沒(méi)有任何限制,它不關(guān)心這個(gè)。一種控制變化的辦法是不可改變的,持久化的數(shù)據(jù)結(jié)構(gòu)。總結(jié)檢測(cè)變化時(shí)開(kāi)發(fā)中的核心問(wèn)題,而框架們以各種方式解決這個(gè)問(wèn)題。因?yàn)榻M件內(nèi)的變化是不被允許的。 AngularJS:臟檢查 我不知道什么更新了,所以當(dāng)更新的時(shí)候,我只能檢查所有的東西。 AngularJS 類(lèi)似于 Ember,當(dāng)狀態(tài)改變的時(shí)候,必須人工去處理。但不同的是,AngularJS 從不同的角度來(lái)...
閱讀 3957·2021-11-22 13:53
閱讀 1688·2021-08-25 09:39
閱讀 2417·2019-08-29 18:36
閱讀 1478·2019-08-26 13:35
閱讀 1220·2019-08-26 11:57
閱讀 1686·2019-08-23 15:57
閱讀 808·2019-08-23 14:55
閱讀 1170·2019-08-23 14:51