應(yīng)用的組件之間經(jīng)常需要共享信息。你通常要用松耦合的技術(shù)來共享信息,比如數(shù)據(jù)綁定和服務(wù)共享。但是有時(shí)候讓一個(gè)組件直接引用另一個(gè)組件還是很有意義的。 例如,你需要通過另一個(gè)組件的直接引用來訪問其屬性或調(diào)用其方法。
在 Angular 中獲取組件引用略微有些棘手。 Angular 組件本身并沒有一棵可以用編程方式檢查或?yàn)g覽的樹。 其父子關(guān)系是通過組件的視圖對(duì)象間接建立的。
每個(gè)組件都有一個(gè)宿主視圖和一些內(nèi)嵌視圖。 組件 A 的內(nèi)嵌視圖可以是組件 B 的宿主視圖,而組件 B 還可以有它自己的內(nèi)嵌視圖。 這意味著每個(gè)組件都有一棵以該組件的宿主視圖為根節(jié)點(diǎn)的視圖樹。
有一些用于在視圖樹中向下導(dǎo)航的 API。 請(qǐng)到 API 參考手冊(cè)中查看 Query、QueryList、ViewChildren 和 ContentChildren。
不存在用于獲取父引用的公共 API。 不過,由于每個(gè)組件的實(shí)例都會(huì)添加到注入器的容器中,因此你可以通過 Angular 的依賴注入來訪問父組件。
本節(jié)描述的就是關(guān)于這種做法的一些技巧。
你可以使用標(biāo)準(zhǔn)的類注入形式來獲取類型已知的父組件。
在下面的例子中,父組件 AlexComponent
具有一些子組件,包括 CathyComponent
:
Path:"parent-finder.component.ts (AlexComponent v.1)" 。
@Component({
selector: 'alex',
template: `
<div class="a">
<h3>{{name}}</h3>
<cathy></cathy>
<craig></craig>
<carol></carol>
</div>`,
})
export class AlexComponent extends Base
{
name = 'Alex';
}
在把 AlexComponent
注入到 CathyComponent
的構(gòu)造函數(shù)中之后,Cathy
可以報(bào)告她是否能訪問 Alex
:
Path:"parent-finder.component.ts (CathyComponent)" 。
@Component({
selector: 'cathy',
template: `
<div class="c">
<h3>Cathy</h3>
{{alex ? 'Found' : 'Did not find'}} Alex via the component class.<br>
</div>`
})
export class CathyComponent {
constructor( @Optional() public alex?: AlexComponent ) { }
}
注意,雖然為了安全起見我們用了 @Optional
限定符,但是范例中仍然會(huì)確認(rèn) alex
參數(shù)是否有值。
如果你不知道具體的父組件類怎么辦?
可復(fù)用組件可能是多個(gè)組件的子組件。想象一個(gè)用于渲染相關(guān)金融工具的突發(fā)新聞的組件。 出于商業(yè)原因,當(dāng)市場(chǎng)上的數(shù)據(jù)流發(fā)生變化時(shí),這些新組件會(huì)頻繁調(diào)用其父組件。
該應(yīng)用可能定義了十幾個(gè)金融工具組件。理想情況下,它們?nèi)紝?shí)現(xiàn)了同一個(gè)基類,你的 NewsComponent 也能理解其 API。
如果能查找實(shí)現(xiàn)了某個(gè)接口的組件當(dāng)然更好。 但那是不可能的。因?yàn)?TypeScript 接口在轉(zhuǎn)譯后的 JavaScript 中不存在,而 JavaScript 不支持接口。 因此,找無可找。
這個(gè)設(shè)計(jì)并不怎么好。 該例子是為了驗(yàn)證組件是否能通過其父組件的基類來注入父組件。
這個(gè)例子中的 CraigComponent 體現(xiàn)了此問題。往回看,你可以看到 Alex 組件擴(kuò)展(繼承)了基類 Base。
Path:"parent-finder.component.ts (Alex class signature)" 。
export class AlexComponent extends Base
CraigComponent
試圖把 Base
注入到它的構(gòu)造函數(shù)參數(shù) alex
中,并匯報(bào)這次注入是否成功了。
Path:"parent-finder.component.ts (CraigComponent)" 。
@Component({
selector: 'craig',
template: `
<div class="c">
<h3>Craig</h3>
{{alex ? 'Found' : 'Did not find'}} Alex via the base class.
</div>`
})
export class CraigComponent {
constructor( @Optional() public alex?: Base ) { }
}
不幸的是,這不行! 范例確認(rèn)了 alex
參數(shù)為空。 因此,你不能通過父組件的基類注入它。
你可以通過父組件的類接口來查找它。
該父組件必須合作,以類接口令牌為名,為自己定義一個(gè)別名提供者。
回憶一下,Angular 總是會(huì)把組件實(shí)例添加到它自己的注入器中,因此以前你才能把 Alex
注入到 Cathy
中。
編寫一個(gè) 別名提供者(一個(gè) provide
對(duì)象字面量,其中有一個(gè) useExisting
定義),創(chuàng)造了另一種方式來注入同一個(gè)組件實(shí)例,并把那個(gè)提供者添加到 AlexComponent @Component()
元數(shù)據(jù)的 providers
數(shù)組中。
Path:"parent-finder.component.ts (AlexComponent providers)" 。
providers: [{ provide: Parent, useExisting: forwardRef(() => AlexComponent) }],
Parent 是該提供者的類接口。 forwardRef
用于打破循環(huán)引用,因?yàn)樵谀銊偛胚@個(gè)定義中 AlexComponent
引用了自身。
Alex
的第三個(gè)子組件 Carol
,把其父組件注入到了自己的 parent
參數(shù)中 —— 和你以前做過的一樣。
Path:"parent-finder.component.ts (CarolComponent class)" 。
export class CarolComponent {
name = 'Carol';
constructor( @Optional() public parent?: Parent ) { }
}
下面是 Alex
及其家人的運(yùn)行效果。
想象一下組件樹的一個(gè)分支:Alice -> Barry -> Carol
。 無論 Alice
還是 Barry
都實(shí)現(xiàn)了類接口 Parent
。
Barry
很為難。他需要訪問他的母親 Alice
,同時(shí)他自己還是 Carol
的父親。 這意味著他必須同時(shí)注入 Parent
類接口來找到 Alice
,同時(shí)還要提供一個(gè) Parent
來滿足 Carol
的要求。
Barry
的代碼如下。
Path:"parent-finder.component.ts (BarryComponent)" 。
const templateB = `
<div class="b">
<div>
<h3>{{name}}</h3>
<p>My parent is {{parent?.name}}</p>
</div>
<carol></carol>
<chris></chris>
</div>`;
@Component({
selector: 'barry',
template: templateB,
providers: [{ provide: Parent, useExisting: forwardRef(() => BarryComponent) }]
})
export class BarryComponent implements Parent {
name = 'Barry';
constructor( @SkipSelf() @Optional() public parent?: Parent ) { }
}
Barry
的 providers
數(shù)組看起來和 Alex
的一樣。 如果你準(zhǔn)備繼續(xù)像這樣編寫別名提供者,就應(yīng)該創(chuàng)建一個(gè)輔助函數(shù)。
現(xiàn)在,注意看 Barry 的構(gòu)造函數(shù)。
//Barry's constructor
constructor( @SkipSelf() @Optional() public parent?: Parent ) { }
//Carol's constructor
constructor( @Optional() public parent?: Parent ) { }
除增加了 @SkipSelf
裝飾器之外,它和 Carol
的構(gòu)造函數(shù)相同。
使用 @SkipSelf
有兩個(gè)重要原因:
它告訴注入器開始從組件樹中高于自己的位置(也就是父組件)開始搜索 Parent
依賴。
如果你省略了 @SkipSelf
裝飾器,Angular 就會(huì)拋出循環(huán)依賴錯(cuò)誤。
Cannot instantiate cyclic dependency! (BethComponent -> Parent -> BethComponent)
下面是 Alice、Barry 及其家人的運(yùn)行效果。
類接口是一個(gè)抽象類,它實(shí)際上用做接口而不是基類。
下面的例子定義了一個(gè)類接口 Parent。
Path:"parent-finder.component.ts (Parent class-interface)" 。
export abstract class Parent { name: string; }
Parent 類接口定義了一個(gè)帶類型的 name 屬性,但沒有實(shí)現(xiàn)它。 這個(gè) name
屬性是父組件中唯一可供子組件調(diào)用的成員。 這樣的窄化接口幫助把子組件從它的父組件中解耦出來。
一個(gè)組件想要作為父組件使用,就應(yīng)該像 AliceComponent
那樣實(shí)現(xiàn)這個(gè)類接口。
Path:"parent-finder.component.ts (AliceComponent class signature)" 。
export class AliceComponent implements Parent
這樣做可以增加代碼的清晰度,但在技術(shù)上并不是必要的。 雖然 AlexComponent
像 Base
類所要求的一樣具有 name 屬性,但它的類簽名中并沒有提及 Parent
。
Path:"parent-finder.component.ts (AlexComponent class signature)" 。
export class AlexComponent extends Base
你很快就會(huì)厭倦為同一個(gè)父組件編寫別名提供者的變體形式,特別是帶有 forwardRef
的那種。
Path:"dependency-injection-in-action/src/app/parent-finder.component.ts" 。
providers: [{ provide: Parent, useExisting: forwardRef(() => AlexComponent) }],
你可以像把這些邏輯抽取到輔助函數(shù)中,就像這樣。
Path:"dependency-injection-in-action/src/app/parent-finder.component.ts" 。
// Helper method to provide the current component instance in the name of a `parentType`.
export function provideParent
(component: any) {
return { provide: Parent, useExisting: forwardRef(() => component) };
}
現(xiàn)在,你可以為組件添加一個(gè)更簡(jiǎn)單、更有意義的父組件提供者。
Path:"dependency-injection-in-action/src/app/parent-finder.component.ts" 。
providers: [ provideParent(AliceComponent) ]
你還可以做得更好。當(dāng)前版本的輔助函數(shù)只能為類接口 Parent 定義別名。 應(yīng)用可能具有多種父組件類型,每個(gè)父組件都有自己的類接口令牌。
這是一個(gè)修訂后的版本,它默認(rèn)為 parent
,但是也能接受另一個(gè)父類接口作為可選的第二參數(shù)。
Path:"dependency-injection-in-action/src/app/parent-finder.component.ts" 。
// Helper method to provide the current component instance in the name of a `parentType`.
// The `parentType` defaults to `Parent` when omitting the second parameter.
export function provideParent
(component: any, parentType?: any) {
return { provide: parentType || Parent, useExisting: forwardRef(() => component) };
}
下面是針對(duì)不同父組件類型的用法。
Path:"dependency-injection-in-action/src/app/parent-finder.component.ts" 。
providers: [ provideParent(BethComponent, DifferentParent) ]
更多建議: