填寫這份《一分鐘調查》,幫我們(開發組)做得更好!去填寫Home

使用 DI 瀏覽元件樹

Navigate the component tree with DI

Marked for archiving

To ensure that you have the best experience possible, this topic is marked for archiving until we determine that it clearly conveys the most accurate information possible.

In the meantime, this topic might be helpful: Hierarchical injectors.

If you think this content should not be archived, please file a GitHub issue.

應用的元件之間經常需要共享資訊。你通常要用鬆耦合的技術來共享資訊,比如資料繫結和服務共享。但是有時候讓一個元件直接參考另一個元件還是很有意義的。 例如,你需要透過另一個元件的直接參考來訪問其屬性或呼叫其方法。

Application components often need to share information. You can often use loosely coupled techniques for sharing information, such as data binding and service sharing, but sometimes it makes sense for one component to have a direct reference to another component. You need a direct reference, for instance, to access values or call methods on that component.

在 Angular 中獲取元件參考略微有些棘手。 Angular 元件本身並沒有一棵可以用程式設計方式檢查或瀏覽的樹。 其父子關係是透過元件的檢視物件間接建立的。

Obtaining a component reference is a bit tricky in Angular. Angular components themselves do not have a tree that you can inspect or navigate programmatically. The parent-child relationship is indirect, established through the components' view objects.

每個元件都有一個宿主檢視和一些內嵌檢視。 元件 A 的內嵌檢視可以是元件 B 的宿主檢視,而元件 B 還可以有它自己的內嵌檢視。 這意味著每個元件都有一棵以該元件的宿主檢視為根節點的檢視樹

Each component has a host view, and can have additional embedded views. An embedded view in component A is the host view of component B, which can in turn have embedded view. This means that there is a view hierarchy for each component, of which that component's host view is the root.

有一些用於在檢視樹中向下導航的 API。 請到 API 參考手冊中檢視 QueryQueryListViewChildrenContentChildren

There is an API for navigating down the view hierarchy. Check out Query, QueryList, ViewChildren, and ContentChildren in the API Reference.

不存在用於獲取父參考的公共 API。 不過,由於每個元件的實例都會新增到注入器的容器中,因此你可以透過 Angular 的依賴注入來訪問父元件。

There is no public API for acquiring a parent reference. However, because every component instance is added to an injector's container, you can use Angular dependency injection to reach a parent component.

本節描述的就是關於這種做法的一些技巧。

This section describes some techniques for doing that.

查詢已知型別的父元件

Find a parent component of known type

你可以使用標準的類別注入形式來獲取型別已知的父元件。

You use standard class injection to acquire a parent component whose type you know.

在下面的例子中,父元件 AlexComponent 具有一些子元件,包括 CathyComponent

In the following example, the parent AlexComponent has several children including a CathyComponent:

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 的建構函式中之後,Cathy 可以報告她是否能訪問 Alex

Cathy reports whether or not she has access to Alex after injecting an AlexComponent into her constructor:

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 限定符,但是現場演練 / 下載範例中仍然會確認 alex 引數是否有值。

Notice that even though the @Optional qualifier is there for safety, the現場演練 / 下載範例confirms that the alex parameter is set.

不能根據父元件的基底類別訪問父元件

Unable to find a parent by its base class

如果你不知道具體的父元件類別怎麼辦?

What if you don't know the concrete parent component class?

可複用元件可能是多個元件的子元件。想象一個用於渲染相關金融工具的突發新聞的元件。 出於商業原因,當市場上的資料流發生變化時,這些新元件會頻繁呼叫其父元件。

A re-usable component might be a child of multiple components. Imagine a component for rendering breaking news about a financial instrument. For business reasons, this news component makes frequent calls directly into its parent instrument as changing market data streams by.

該應用可能定義了十幾個金融工具元件。理想情況下,它們全都實現了同一個基底類別,你的 NewsComponent 也能理解其 API。

The app probably defines more than a dozen financial instrument components. If you're lucky, they all implement the same base class whose API your NewsComponent understands.

如果能查詢實現了某個介面的元件當然更好。 但那是不可能的。因為 TypeScript 介面在轉譯後的 JavaScript 中不存在,而 JavaScript 不支援介面。 因此,找無可找。

Looking for components that implement an interface would be better. That's not possible because TypeScript interfaces disappear from the transpiled JavaScript, which doesn't support interfaces. There's no artifact to look for.

這個設計並不怎麼好。 該例子是為了驗證元件是否能透過其父元件的基底類別來注入父元件

This isn't necessarily good design. This example is examining whether a component can inject its parent via the parent's base class.

這個例子中的 CraigComponent 體現了此問題。往回看,你可以看到 Alex 元件擴充套件繼承)了基底類別 Base

The sample's CraigComponent explores this question. Looking back, you see that the Alex component extends (inherits) from a class named Base.

parent-finder.component.ts (Alex class signature)
      
      export class AlexComponent extends Base
    

CraigComponent 試圖把 Base 注入到它的建構函式引數 alex 中,並彙報這次注入是否成功了。

The CraigComponent tries to inject Base into its alex constructor parameter and reports if it succeeded.

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 ) { }
}
    

不幸的是,這不行!現場演練 / 下載範例確認了 alex 引數為空。 因此,你不能透過父元件的基底類別注入它

Unfortunately, this doesn't work. The現場演練 / 下載範例confirms that the alex parameter is null. You cannot inject a parent by its base class.

根據父元件的類別介面查詢它

Find a parent by its class interface

你可以透過父元件的類別介面來查詢它。

You can find a parent component with a class interface.

該父元件必須合作,以類別介面令牌為名,為自己定義一個別名提供者

The parent must cooperate by providing an alias to itself in the name of a class interface token.

回憶一下,Angular 總是會把元件實例新增到它自己的注入器中,因此以前你才能把 Alex 注入到 Cathy 中。

Recall that Angular always adds a component instance to its own injector; that's why you could inject Alex into Cathy earlier.

編寫一個 別名提供者(一個 provide 物件字面量,其中有一個 useExisting 定義),創造了另一種方式來注入同一個元件實例,並把那個提供者新增到 AlexComponent @Component() 元資料的 providers 陣列中。

Write an alias provider—a provide object literal with a useExisting definition—that creates an alternative way to inject the same component instance and add that provider to the providers array of the @Component() metadata for the AlexComponent.

parent-finder.component.ts (AlexComponent providers)
      
      providers: [{ provide: Parent, useExisting: forwardRef(() => AlexComponent) }],
    

Parent 是該提供者的類別介面。 forwardRef 用於打破迴圈參考,因為在你剛才這個定義中 AlexComponent 參考了自身。

Parent is the provider's class interface token. The forwardRef breaks the circular reference you just created by having the AlexComponent refer to itself.

Alex 的第三個子元件 Carol,把其父元件注入到了自己的 parent 引數中 —— 和你以前做過的一樣。

Carol, the third of Alex's child components, injects the parent into its parent parameter, the same way you've done it before.

parent-finder.component.ts (CarolComponent class)
      
      export class CarolComponent {
  name = 'Carol';
  constructor( @Optional() public parent?: Parent ) { }
}
    

下面是 Alex 及其家人的執行效果。

Here's Alex and family in action.

使用 @SkipSelf() 在樹中查詢父元件

Find a parent in a tree with @SkipSelf()

想象一下元件樹的一個分支:Alice -> Barry -> Carol。 無論 Alice 還是 Barry 都實現了類別介面 Parent

Imagine one branch of a component hierarchy: Alice -> Barry -> Carol. Both Alice and Barry implement the Parent class interface.

Barry 很為難。他需要訪問他的母親 Alice,同時他自己還是 Carol 的父親。 這意味著他必須同時注入 Parent 類別介面來找到 Alice,同時還要提供一個 Parent 來滿足 Carol 的要求。

Barry is the problem. He needs to reach his parent, Alice, and also be a parent to Carol. That means he must both inject the Parent class interface to get Alice and provide a Parent to satisfy Carol.

Barry 的程式碼如下。

Here's Barry.

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 ) { }
}
    

Barryproviders 陣列看起來和 Alex 的一樣。 如果你準備繼續像這樣編寫別名提供者,就應該建立一個輔助函式。

Barry's providers array looks just like Alex's. If you're going to keep writing alias providers like this you should create a helper function.

現在,注意看 Barry 的建構函式。

For now, focus on Barry's constructor.

      
      constructor( @SkipSelf() @Optional() public parent?: Parent ) { }
    

除增加了 @SkipSelf 裝飾器之外,它和 Carol 的建構函式相同。

It's identical to Carol's constructor except for the additional @SkipSelf decorator.

使用 @SkipSelf 有兩個重要原因:

@SkipSelf is essential for two reasons:

  1. 它告訴注入器開始從元件樹中高於自己的位置(也就是父元件)開始搜尋 Parent 依賴。

    It tells the injector to start its search for a Parent dependency in a component above itself, which is what parent means.

  2. 如果你省略了 @SkipSelf 裝飾器,Angular 就會丟擲迴圈依賴錯誤。

    Angular throws a cyclic dependency error if you omit the @SkipSelf decorator.

    NG0200: Circular dependency in DI detected for BethComponent. Dependency path: BethComponent -> Parent -> BethComponent

下面是 AliceBarry 及其家人的執行效果。

Here's Alice, Barry, and family in action.

父類別介面

Parent class interface

已經學過,類別介面是一個抽象類別,它實際上用做介面而不是基底類別。

You learned earlier that a class interface is an abstract class used as an interface rather than as a base class.

下面的例子定義了一個類別介面 Parent

The example defines a Parent class interface.

parent-finder.component.ts (Parent class-interface)
      
      export abstract class Parent { abstract name: string; }
    

Parent 類別介面定義了一個帶型別的 name 屬性,但沒有實現它。 這個 name 屬性是父元件中唯一可供子元件呼叫的成員。 這樣的窄化介面幫助把子元件從它的父元件中解耦出來。

The Parent class interface defines a name property with a type declaration but no implementation. The name property is the only member of a parent component that a child component can call. Such a narrow interface helps decouple the child component class from its parent components.

一個元件想要作為父元件使用,就應該AliceComponent 那樣實現這個類別介面。

A component that could serve as a parent should implement the class interface as the AliceComponent does.

parent-finder.component.ts (AliceComponent class signature)
      
      export class AliceComponent implements Parent
    

這樣做可以增加程式碼的清晰度,但在技術上並不是必要的。 雖然 AlexComponentBase 類別所要求的一樣具有 name 屬性,但它的類別簽名中並沒有提及 Parent

Doing so adds clarity to the code. But it's not technically necessary. Although AlexComponent has a name property, as required by its Base class, its class signature doesn't mention Parent.

parent-finder.component.ts (AlexComponent class signature)
      
      export class AlexComponent extends Base
    

AlexComponent 應該實現 Parent 才是一種正確的風格。 這個例子中之所以沒這樣做,只是為了證明即使沒有宣告介面,程式碼也可以編譯和執行。

AlexComponent should implement Parent as a matter of proper style. It doesn't in this example only to demonstrate that the code will compile and run without the interface.