摘要:裝飾器我們?yōu)樯兑懻撛刈⑷肫鞫皇茄b飾器這是因?yàn)闀?huì)把元素注入器依賴解析過(guò)程限制在當(dāng)前組件視圖內(nèi)。但是一旦使用了裝飾器,整個(gè)依賴解析過(guò)程就會(huì)在第一階段完成后停止解析,也就是說(shuō),元素注入器只在組件視圖內(nèi)解析依賴,然后就停止解析工作。
原文鏈接:A curious case of the @Host decorator and Element Injectors in Angular
我們知道,Angular 依賴注入機(jī)制包含 @Optional 和 @Self 等影響依賴解析過(guò)程的裝飾器,盡管它們字面意思就直接解釋了其作用,但是 @Host 卻困擾了我好久。我在其源碼注釋中看到該裝飾器的 描述:
Specifies that an injector should retrieve a dependency from any injector until reaching the host element of the current component.
由于網(wǎng)上大多數(shù)教程都提到 Angular 的模塊注入器和組件注入器,所以我認(rèn)為 @Host 應(yīng)該和多級(jí)組件注入器相關(guān)。我猜想 @Host 裝飾器可以用在子組件內(nèi),來(lái)限制只能在它自身和其父組件注入器內(nèi)解析依賴,所以我做了個(gè) 小示例 來(lái)驗(yàn)證這個(gè)假設(shè):
@Component({ selector: "my-app", template: ``, providers: [MyAppService] }) export class AppComponent {} @Component({selector: "a-comp", ...}) export class AComponent { constructor(@Host() s: MyAppService) {} }
但是居然報(bào)錯(cuò) No provider for MyAppServic,有意思的是,如果我 移除 @Host 裝飾器,MyAppService 就可以順利從父組件注入器內(nèi)解析出來(lái)。發(fā)生了什么?為了弄清楚,我擼起袖子開(kāi)始調(diào)查。讓我與你分享我究竟發(fā)現(xiàn)了什么。
我現(xiàn)在就告訴你關(guān)鍵點(diǎn)就在上文 @Host 裝飾器描述中的 "until" 一詞上:
…retrieve a dependency from any injector until reaching the host element
它意思是 @Host 裝飾器會(huì)讓依賴解析過(guò)程限制在當(dāng)前組件模板,甚至都不包括其宿主元素(注:在宿主元素 a-comp 上綁定含有 MyAppService 服務(wù)的指令 ADirective,是可以在 AComponent 的構(gòu)造函數(shù)中解析出被 @Host 裝飾的 MyAppService 服務(wù))。這就是我的示例中錯(cuò)誤原因——Angular 不會(huì)從其宿主父組件注入器中解析依賴。
所以現(xiàn)在我們知道 @Host 裝飾器不可以用來(lái)在子組件中解析來(lái)自父組件的依賴提供者,意味著該裝飾器的依賴解析機(jī)制不可以用于多級(jí)組件注入器。
所以應(yīng)該使用什么樣的多級(jí)注入器?
實(shí)際上,除了模塊注入器和組件注入器,Angular 還提供了第三種注入器,即多級(jí)元素注入器,它是由 HTML 元素和指令共同創(chuàng)建的。
元素注入器Angular 會(huì)按照三個(gè)階段來(lái)解析依賴,起始階段就是使用多級(jí)元素注入器,然后是多級(jí)組件注入器,最后是多級(jí)模塊注入器。如果你對(duì)整個(gè)解析過(guò)程感興趣,我強(qiáng)烈建議你閱讀 Alexey Zuev 寫的這篇 深度好文。
對(duì)組件和模塊注入器解析依賴的后兩個(gè)階段,我們應(yīng)該很熟悉了。當(dāng)你延遲加載模塊時(shí),Angular 會(huì)創(chuàng)建多級(jí)模塊注入器,詳細(xì)過(guò)程我已在 my talk at NgConf 做了演講,并 寫了篇文章。多級(jí)組件注入器是由模板中嵌套組件創(chuàng)建的,就內(nèi)部實(shí)現(xiàn)而言,組件注入器也可稱為視圖注入器,稍后將會(huì)解釋原因。
另外,多級(jí)元素注入器是 Angular 依賴注入系統(tǒng)內(nèi)很少知道的功能,因?yàn)槲臋n里沒(méi)寫,但是這種注入器在依賴注入系統(tǒng)的起始階段就使用了。這些多級(jí)元素注入器被用來(lái)解析由 @Host 裝飾的依賴,所以讓我們仔細(xì)研究下這種注入器。
一個(gè)元素注入器你可能從我 之前一篇文章 中知道,Angular 內(nèi)部使用一種叫視圖或組件視圖的數(shù)據(jù)結(jié)構(gòu)來(lái)表示組件(注:可查看源碼中 ViewDefinition 接口)。實(shí)際上,這就是把組件注入器稱為視圖注入器的由來(lái),視圖對(duì)象主要用來(lái)表示組件模板中由 HTML 元素創(chuàng)建的 DOM 節(jié)點(diǎn)的集合(注:@angular/compiler 會(huì)編譯你寫的組件,生成的結(jié)果由 ViewDefinition 接口來(lái)表示,不需要知道其編譯過(guò)程,只需知道你寫的帶有 @Component 裝飾的類不編譯為新的類是無(wú)法直接運(yùn)行的,且 HTML 模板就存在于新類的屬性里,即 Compile HTML+Class into New Class。正因?yàn)?@angular/compiler 編譯功能強(qiáng)大,所以可以在 HTML 模板里寫很多不符合 HTML 語(yǔ)法的 HTML 代碼,比如綁定指令等等)。所以,每一個(gè)視圖對(duì)象內(nèi)部是由不同種類視圖節(jié)點(diǎn)組成的(注:ViewDefinition 表示編譯后的視圖對(duì)象,視圖又是由節(jié)點(diǎn)組成的,@angular/core 使用 NodeDef 接口來(lái)表示所有節(jié)點(diǎn),而節(jié)點(diǎn)又分很多種,使用 NodeFlags 來(lái)表示,其中最常見(jiàn)的 TypeElement 類型就是來(lái)標(biāo)識(shí) HTML 元素,使用 ElementDef 接口來(lái)表示),最常用的視圖節(jié)點(diǎn)類型是元素節(jié)點(diǎn),用來(lái)表示對(duì)應(yīng)的 DOM 元素,下圖表示視圖和 DOM 兩者之間的關(guān)系:
每一個(gè)視圖節(jié)點(diǎn)都是由節(jié)點(diǎn)定義對(duì)象實(shí)例化后創(chuàng)建的,節(jié)點(diǎn)定義對(duì)象包含描述節(jié)點(diǎn)的元數(shù)據(jù)。比如,像 element 類型的節(jié)點(diǎn)通常用來(lái)表示 DOM 元素,而這些元數(shù)據(jù)是由 @angular/compiler 的編譯器編譯組件模板和附著在模板上的指令生成的。下圖表示視圖節(jié)點(diǎn)定義與其對(duì)象之間的關(guān)系:
元素節(jié)點(diǎn)定義描述了一個(gè)有趣的功能:
In Angular, a node definition that describes an HTML element defines its own injector. In other words, an HTML element in a component’s template defines its own element injector. And this injector can populated with providers by applying one or more directives on the corresponding HTML element.
讓我們看示例。
假設(shè)組件模板中有個(gè) div 元素,并且有兩個(gè)指令掛載到上面:
@Component({ selector: "my-app", template: `` }) export class AppComponent {} @Directive({ selector: "[a]" }) export class ADirective {} @Directive({ selector: "[b]" }) export class BDirective {}
@angular/compiler 的編譯器會(huì)編譯模板生成視圖,該視圖中 DOM 元素 div 對(duì)應(yīng)的元素節(jié)點(diǎn)定義對(duì)象包含如下元數(shù)據(jù)(注:可查看 ElementDef 接口中的 name 和 publicProviders 屬性):
const DivElementNodeDefinition = { element: { name: "div", publicProviders: { ADirective: referenceToADirectiveProviderDefinition, BDirective: referenceToBDirectiveProviderDefinition } } }
正如你所見(jiàn),節(jié)點(diǎn)對(duì)象定義了 element.publicProviders 屬性,包含兩個(gè)服務(wù)提供者 ADirective 和 BDirective,該屬性作用類似于一個(gè)注入器,而 referenceToADirectiveProviderDefinition 和 referenceToBDirectiveProviderDefinition 就是附著在 div 元素上兩個(gè)指令的實(shí)例。由于它們是由同一個(gè)元素注入器解析,所以 你可以把其中一個(gè)指令注入到另一個(gè)指令中。當(dāng)然,你不能在兩個(gè)指令中相互注入依賴,因?yàn)檫@會(huì)導(dǎo)致依賴死鎖。
所以,下圖說(shuō)明了我們現(xiàn)在擁有的東西:
注意宿主元素 app-comp 存在于 AppComponentView 之外,因?yàn)樗菍儆诟附M件視圖內(nèi)的。
現(xiàn)在如果 ADirective 也包含服務(wù)提供者會(huì)發(fā)生什么?
@Directive({ selector: "[a]", providers: [ADirService] }) export class ADirective {}
正如你所料,這個(gè)服務(wù)會(huì)被包含進(jìn)由 div 創(chuàng)建的元素注入器里:
const divElementNodeDefinition = { element: { name: "div", publicProviders: { ADirService: referenceToADirServiceProviderDefinition ADirective: referenceToADirectiveProviderDefinition, ADirective: referenceToADirectiveProviderDefinition } } }
再一次放圖,下圖是現(xiàn)在的結(jié)果:
多級(jí)元素注入器上文我們只有一個(gè) HTML 元素,嵌套 HTML 元素組成了 DOM 元素層級(jí),組件視圖內(nèi)的這些 DOM 元素組成了 Angular 依賴注入系統(tǒng)內(nèi)多級(jí)元素注入器。
讓我們看示例。
假設(shè)組件模板中有父子元素 div,同時(shí),還有兩個(gè)指令 A 和 B。A 指令附著在父元素 div 上并提供 ADirService 服務(wù),B 指令附著在子元素 div 上但不提供任何服務(wù)。
下面代碼展示了具體內(nèi)容:
@Component({ selector: "my-app", template: `` }) export class AppComponent {} @Directive({ selector: "[a]", providers: [ADirService] }) export class ADirective {} @Directive({ selector: "[b]" }) export class BDirective {}
如果我們?nèi)ヌ骄?@angular/compiler 編譯模板創(chuàng)建的元素節(jié)點(diǎn)定義對(duì)象,會(huì)發(fā)現(xiàn)存在兩個(gè) element 類型節(jié)點(diǎn)來(lái)描述 div 元素:
const viewDefinitionNodes = [ { // element definition for the parent div element: { name: `div`, publicProviders: { ADirective: referenceToADirectiveProviderDefinition, ADirService: referenceToADirServiceProviderDefinition, } } }, { // element definition for the child div element: { name: `div`, publicProviders: { BDirective: referenceToBDirectiveProviderDefinition } } } ]
正如上文中發(fā)現(xiàn)的,每一個(gè) div 元素定義都有個(gè) publicProviders 作為依賴注入容器。由于附著在父 div 元素還提供了 ADirService 服務(wù),所以該服務(wù)也被加到父元素 div 的元素注入器內(nèi)。
嵌套 HTML 結(jié)構(gòu)創(chuàng)建了多級(jí)元素注入器
有趣的是,子組件也創(chuàng)建了一個(gè)元素注入器,成了多級(jí)元素注入器的一部分,例如,如下代碼:
adir 指令提供一個(gè)服務(wù),創(chuàng)建了兩個(gè)層級(jí)元素注入器——上層級(jí)父注入器創(chuàng)建在 div 元素上,下層級(jí)子注入器創(chuàng)建在 a-comp 元素上,這并不奇怪,因?yàn)?組件也僅僅是帶有指令的 HTML 元素。
創(chuàng)建元素注入器當(dāng) Angular 為嵌套 HTML 元素創(chuàng)建注入器時(shí),該注入器要么會(huì)繼承父注入器,要么直接把父注入器賦值給子注入器。如果子元素上掛載指令且該指令提供依賴服務(wù),則子注入器會(huì)繼承父注入器,也就是說(shuō),由掛載指令并提供服務(wù)的子元素創(chuàng)建的元素注入器,是會(huì)繼承父注入器的。另外,沒(méi)有必要為子組件多帶帶創(chuàng)建一個(gè)注入器,如有必要,可以直接使用父注入器來(lái)解析依賴。
下圖說(shuō)明了這個(gè)過(guò)程:
依賴解析過(guò)程安裝組件視圖內(nèi)的多級(jí)元素注入器,會(huì)大大簡(jiǎn)化依賴解析過(guò)程。Angular 使用 JavaScript 的原型鏈的屬性查詢機(jī)制來(lái)解析依賴,而不是一層層去查找父注入器去解析依賴:
elDef.element.publicProviders[tokenKey]
由于 JavaScript 的工作方式,訪問(wèn) publicProviders 對(duì)象 key 對(duì)應(yīng)的值,會(huì)直接從父元素注入器或原型鏈中解析出來(lái)。
@Host 裝飾器我們?yōu)樯兑懻撛刈⑷肫鞫皇?@Host 裝飾器?這是因?yàn)?@Host 會(huì)把元素注入器依賴解析過(guò)程限制在當(dāng)前組件視圖內(nèi)。在一般依賴解析過(guò)程中,如果組件視圖內(nèi)的元素注入器不能解析一個(gè)令牌,Angular 依賴解析系統(tǒng)會(huì)遍歷父視圖,去使用組件/視圖注入器來(lái)解析令牌,如果還沒(méi)找到依賴,則遍歷模塊注入器去查找依賴。但是一旦使用了 @Host 裝飾器,整個(gè)依賴解析過(guò)程就會(huì)在第一階段完成后停止解析,也就是說(shuō),元素注入器只在組件視圖內(nèi)解析依賴,然后就停止解析工作。
示例@Host 在表單指令里被大量使用,比如,往 ngModel 指令里注入一個(gè)表單容器,并把由該指令實(shí)例化的表單控件對(duì)象(FormControl)注入到表單對(duì)象內(nèi),典型的模板驅(qū)動(dòng)表單代碼如下:
實(shí)際上, NgForm 指令的選擇器與 form DOM 元素匹配,該指令包含一個(gè)服務(wù)提供者,把自身注冊(cè)為 ControlContainer 令牌指向的服務(wù):
@Directive({ selector: "form", providers: [ { provide: ControlContainer, useExisting: NgForm } ] }) export class NgForm {}
而 ngModel 指令也是用 ControlContainer 令牌來(lái)注入依賴,并將自身注冊(cè)為表單的一個(gè)控件:
@Directive({ selector: "[ngModel]", }) export class NgModel { constructor(@Optional() @Host() parent: ControlContainer) {} private _setUpControl(): void { ... this.parent.formDirective.addControl(this); } }
正如你所見(jiàn),@Host 裝飾器會(huì)把依賴解析過(guò)程限制在當(dāng)前組件視圖內(nèi),大多數(shù)情況下,這是預(yù)期的情況,但是有時(shí)候你需要在嵌套表單里注入來(lái)自父組件的表單對(duì)象。Alexey Zuev 找到了解決方案并 寫了篇文章。
上文說(shuō)到的文章還提到了另一個(gè)有意思的事情,如果我稍稍修改文章開(kāi)頭說(shuō)到的那個(gè)示例,把 MyAppService 注冊(cè)在 viewProviders 而不是 providers 里:
@Component({ selector: "my-app", template: ``, viewProviders: [MyAppService] }) export class AppComponent {} @Component({selector: "a-comp", ...}) export class AComponent { constructor(@Host() s: MyAppService) {} }
MyAppService 依賴就可以從父組件中被成功的解析出來(lái)。
這是因?yàn)?Angular 在解析由 @Host 裝飾的依賴時(shí),會(huì)針對(duì)當(dāng)前組件(注:原文是父組件,應(yīng)該是當(dāng)前組件)的 viewProviders 做 額外的檢查:
// check @Host restriction if (!result) { if (!dep.isHost || this.viewContext.component.isHost || this.viewContext.component.type.reference === tokenReference(dep.token !) || // this line this.viewContext.viewProviders.get(tokenReference(dep.token !)) != null) { <------ result = dep; } else { result = dep.isOptional ? result = {isValue: true, value: null} : null; } }
本文作者敘述方式有點(diǎn)亂,容易導(dǎo)致不了解 Angular 依賴注入(Dependency Injection)的人被搞的一臉懵逼??傊?,DI 系統(tǒng)按照使用順序包括三種注入器:元素注入器,組件注入器和模塊注入器,而 @Host 裝飾器會(huì)限制只使用元素注入器來(lái)解析依賴,如果當(dāng)前組件依賴于被 @Host 修飾的依賴,或模板被綁定了指令且該指令依賴于被 @Host 修飾的依賴,就會(huì)報(bào)錯(cuò)解析不了該依賴(因?yàn)閯倓傉f(shuō)了,@Host 裝飾器會(huì)限制只使用元素注入器來(lái)解析依賴,不會(huì)繼續(xù)使用組件注入器從父組件那拿依賴),解決方案是可以在當(dāng)前組件的 viewProviders 屬性中提供這個(gè)依賴(因?yàn)樵创a中寫了 @Host 修飾的依賴會(huì)最后還從 viewProviders 屬性中看看有沒(méi)有這個(gè)依賴)。寫了個(gè) stackblitz demo,可以照著本文敘述玩一玩。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/96538.html
摘要:注入器樹(shù)大多數(shù)開(kāi)發(fā)者知道,會(huì)創(chuàng)建根注入器,根注入器內(nèi)的服務(wù)都是單例的。也會(huì)被添加到這個(gè)根注入器對(duì)象內(nèi),它主要用來(lái)創(chuàng)建動(dòng)態(tài)組件,因?yàn)樗鎯?chǔ)了屬性指向的組件數(shù)組。為了理解依賴解析算法,我們首先需要知道視圖和父視圖元素概念。 What you always wanted to know about Angular Dependency Injection tree showImg(https...
摘要:三的洋蔥模型這里簡(jiǎn)單講講在中是如何分層的,也就是說(shuō)請(qǐng)求到達(dá)服務(wù)端后如何層層處理,直到響應(yīng)請(qǐng)求并將結(jié)果返回客戶端。從而使得端支持跨域等。 ??最近已經(jīng)使用過(guò)一段時(shí)間的nestjs,讓人寫著有一種java spring的感覺(jué),nestjs可以使用express的所有中間件,此外完美的支持typescript,與數(shù)據(jù)庫(kù)關(guān)系映射typeorm配合使用可以快速的編寫一個(gè)接口網(wǎng)關(guān)。本文會(huì)介紹一下作...
摘要:三的洋蔥模型這里簡(jiǎn)單講講在中是如何分層的,也就是說(shuō)請(qǐng)求到達(dá)服務(wù)端后如何層層處理,直到響應(yīng)請(qǐng)求并將結(jié)果返回客戶端。從而使得端支持跨域等。 ??最近已經(jīng)使用過(guò)一段時(shí)間的nestjs,讓人寫著有一種java spring的感覺(jué),nestjs可以使用express的所有中間件,此外完美的支持typescript,與數(shù)據(jù)庫(kù)關(guān)系映射typeorm配合使用可以快速的編寫一個(gè)接口網(wǎng)關(guān)。本文會(huì)介紹一下作...
摘要:在探索抽象類前,先了解下如何在組件指令中獲取這些抽象類。下面示例描述在組建模板中如何創(chuàng)建如同其他抽象類一樣,通過(guò)屬性綁定元素,比如上例中,綁定的是會(huì)被渲染為注釋的元素,所以輸出也將是。你可以使用查詢模板引用變量來(lái)獲得抽象類。 原文鏈接:Exploring Angular DOM manipulation techniques using ViewContainerRef如果想深入學(xué)習(xí) ...
閱讀 3025·2021-11-23 09:51
閱讀 3625·2021-10-13 09:39
閱讀 2513·2021-09-22 15:06
閱讀 894·2019-08-30 15:55
閱讀 3165·2019-08-30 15:44
閱讀 1794·2019-08-30 14:05
閱讀 3450·2019-08-29 15:24
閱讀 2373·2019-08-29 12:44