成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

[譯] Angular 的 @Host 裝飾器和元素注入器

marek / 1615人閱讀

摘要:裝飾器我們?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ù)提供者 ADirectiveBDirective,該屬性作用類似于一個(gè)注入器,而 referenceToADirectiveProviderDefinitionreferenceToBDirectiveProviderDefinition 就是附著在 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è)指令 AB。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

相關(guān)文章

  • [] 關(guān)于 Angular 依賴注入你需要知道

    摘要:注入器樹(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...

    Edison 評(píng)論0 收藏0
  • 從Express到Nestjs,談?wù)凬estjs設(shè)計(jì)思想和使用方法

    摘要:三的洋蔥模型這里簡(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ì)介紹一下作...

    chanjarster 評(píng)論0 收藏0
  • 從Express到Nestjs,談?wù)凬estjs設(shè)計(jì)思想和使用方法

    摘要:三的洋蔥模型這里簡(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ì)介紹一下作...

    wawor4827 評(píng)論0 收藏0
  • [] 探索 Angular 使用 ViewContainerRef 操作 DOM

    摘要:在探索抽象類前,先了解下如何在組件指令中獲取這些抽象類。下面示例描述在組建模板中如何創(chuàng)建如同其他抽象類一樣,通過(guò)屬性綁定元素,比如上例中,綁定的是會(huì)被渲染為注釋的元素,所以輸出也將是。你可以使用查詢模板引用變量來(lái)獲得抽象類。 原文鏈接:Exploring Angular DOM manipulation techniques using ViewContainerRef如果想深入學(xué)習(xí) ...

    wind5o 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<