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

多級注入器

Hierarchical injectors

Angular 中的注入器有一些規則,你可以利用這些規則來在應用程式中獲得所需的可注入物件可見性。通過了解這些規則,可以確定應在哪個 NgModule、元件或指令中宣告服務提供者。

Injectors in Angular have rules that you can leverage to achieve the desired visibility of injectables in your apps. By understanding these rules, you can determine in which NgModule, Component or Directive you should declare a provider.

兩個注入器層次結構

Two injector hierarchies

Angular 中有兩個注入器層次結構:

There are two injector hierarchies in Angular:

  1. ModuleInjector 層次結構 —— 使用 @NgModule()@Injectable() 註解在此層次結構中配置 ModuleInjector

    ModuleInjector hierarchy—configure a ModuleInjector in this hierarchy using an @NgModule() or @Injectable() annotation.

  2. ElementInjector 層次結構 —— 在每個 DOM 元素上隱式建立。除非你在 @Directive()@Component()providers 屬性中進行配置,否則預設情況下,ElementInjector 為空。

    ElementInjector hierarchy—created implicitly at each DOM element. An ElementInjector is empty by default unless you configure it in the providers property on @Directive() or @Component().

ModuleInjector

可以透過以下兩種方式之一配置 ModuleInjector

The ModuleInjector can be configured in one of two ways:

搖樹優化與 @Injectable()

Tree-shaking and @Injectable()

使用 @Injectable()providedIn 屬性優於 @NgModule()providers 陣列,因為使用 @Injectable()providedIn 時,優化工具可以進行搖樹優化,從而刪除你的應用程式中未使用的服務,以減小捆綁套件尺寸。

Using the @Injectable() providedIn property is preferable to the @NgModule() providers array because with @Injectable() providedIn, optimization tools can perform tree-shaking, which removes services that your app isn't using and results in smaller bundle sizes.

搖樹優化對於函式庫特別有用,因為使用該函式庫的應用程式不需要注入它。在 服務與依賴注入簡介瞭解關於可搖樹優化的提供者的更多資訊。

Tree-shaking is especially useful for a library because the application which uses the library may not have a need to inject it. Read more about tree-shakable providers in Introduction to services and dependency injection.

ModuleInjector@NgModule.providersNgModule.imports 屬性配置。ModuleInjector 是可以透過 NgModule.imports 遞迴找到的所有 providers 陣列的扁平化。

ModuleInjector is configured by the @NgModule.providers and NgModule.imports property. ModuleInjector is a flattening of all of the providers arrays which can be reached by following the NgModule.imports recursively.

ModuleInjector 是在延遲載入其它 @NgModules 時建立的。

Child ModuleInjectors are created when lazy loading other @NgModules.

使用 @Injectable()providedIn 屬性提供服務的方式如下:

Provide services with the providedIn property of @Injectable() as follows:

import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' // <--provides this service in the root ModuleInjector }) export class ItemService { name = 'telephone'; }
      
      import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'  // <--provides this service in the root ModuleInjector
})
export class ItemService {
  name = 'telephone';
}
    

@Injectable() 裝飾器標識服務類別。該 providedIn 屬性配置指定的 ModuleInjector,這裡的 root 會把讓該服務在 root ModuleInjector 上可用。

The @Injectable() decorator identifies a service class. The providedIn property configures a specific ModuleInjector, here root, which makes the service available in the root ModuleInjector.

平臺注入器

Platform injector

root 之上還有兩個注入器,一個是額外的 ModuleInjector,一個是 NullInjector()

There are two more injectors above root, an additional ModuleInjector and NullInjector().

思考下 Angular 要如何透過 main.ts 中的如下程式碼引導應用程式:

Consider how Angular bootstraps the app with the following in main.ts:

platformBrowserDynamic().bootstrapModule(AppModule).then(ref => {...})
      
      platformBrowserDynamic().bootstrapModule(AppModule).then(ref => {...})
    

bootstrapModule() 方法會建立一個由 AppModule 配置的注入器作為平臺注入器的子注入器。也就是 root ModuleInjector

The bootstrapModule() method creates a child injector of the platform injector which is configured by the AppModule. This is the root ModuleInjector.

platformBrowserDynamic() 方法建立一個由 PlatformModule 配置的注入器,該注入器包含特定平臺的依賴項。這允許多個應用共享同一套平臺配置。例如,無論你執行多少個應用程式,瀏覽器都只有一個 URL 欄。你可以使用 platformBrowser() 函式提供 extraProviders,從而在平臺級別配置特定平臺的額外提供者。

The platformBrowserDynamic() method creates an injector configured by a PlatformModule, which contains platform-specific dependencies. This allows multiple apps to share a platform configuration. For example, a browser has only one URL bar, no matter how many apps you have running. You can configure additional platform-specific providers at the platform level by supplying extraProviders using the platformBrowser() function.

層次結構中的下一個父注入器是 NullInjector(),它是樹的頂部。如果你在樹中向上走了很遠,以至於要在 NullInjector() 中尋找服務,那麼除非使用 @Optional(),否則將收到錯誤訊息,因為最終所有東西都將以 NullInjector() 結束並返回錯誤,或者對於 @Optional(),返回 null。關於 @Optional() 的更多資訊,請參閱本指南的 @Optional() 部分

The next parent injector in the hierarchy is the NullInjector(), which is the top of the tree. If you've gone so far up the tree that you are looking for a service in the NullInjector(), you'll get an error unless you've used @Optional() because ultimately, everything ends at the NullInjector() and it returns an error or, in the case of @Optional(), null. For more information on @Optional(), see the @Optional() section of this guide.

下圖展示了前面各段落描述的 root ModuleInjector 及其父注入器之間的關係。

The following diagram represents the relationship between the root ModuleInjector and its parent injectors as the previous paragraphs describe.

雖然 root 是一個特殊的別名,但其它 ModuleInjector 都沒有別名。每當建立動態載入元件時,你還會建立 ModuleInjector,比如路由器,它還會建立子 ModuleInjector

While the name root is a special alias, other ModuleInjectors don't have aliases. You have the option to create ModuleInjectors whenever a dynamically loaded component is created, such as with the Router, which will create child ModuleInjectors.

無論是使用 bootstrapModule() 的方法配置它,還是將所有提供者都用 root 註冊到其自己的服務中,所有請求最終都會轉發到 root 注入器。

All requests forward up to the root injector, whether you configured it with the bootstrapModule() method, or registered all providers with root in their own services.

@Injectable() vs. @NgModule()

如果你在 AppModule@NgModule() 中配置應用級提供者,它就會覆蓋一個在 @Injectable()root 元資料中配置的提供者。你可以用這種方式,來配置供多個應用共享的服務的非預設提供者。

If you configure an app-wide provider in the @NgModule() of AppModule, it overrides one configured for root in the @Injectable() metadata. You can do this to configure a non-default provider of a service that is shared with multiple apps.

下面的例子中,透過把 location 策略 的提供者新增到 AppModuleproviders 列表中,為路由器配置了非預設的 location 策略

Here is an example of the case where the component router configuration includes a non-default location strategy by listing its provider in the providers list of the AppModule.

providers: [ { provide: LocationStrategy, useClass: HashLocationStrategy } ]
src/app/app.module.ts (providers)
      
      providers: [
  { provide: LocationStrategy, useClass: HashLocationStrategy }
]
    

ElementInjector

Angular 會為每個 DOM 元素隱式建立 ElementInjector

Angular creates ElementInjectors implicitly for each DOM element.

可以用 @Component() 裝飾器中的 providersviewProviders 屬性來配置 ElementInjector 以提供服務。例如,下面的 TestComponent 透過提供此服務來配置 ElementInjector

Providing a service in the @Component() decorator using its providers or viewProviders property configures an ElementInjector. For example, the following TestComponent configures the ElementInjector by providing the service as follows:

@Component({ ... providers: [{ provide: ItemService, useValue: { name: 'lamp' } }] }) export class TestComponent
      
      @Component({
  ...
  providers: [{ provide: ItemService, useValue: { name: 'lamp' } }]
})
export class TestComponent
    

注意:請參閱解析規則部分,以瞭解 ModuleInjector 樹和 ElementInjector 樹之間的關係。

Note: Please see the resolution rules section to understand the relationship between the ModuleInjector tree and the ElementInjector tree.

在元件中提供服務時,可以透過 ElementInjector 在該元件實例處使用該服務。根據解析規則部分描述的可見性規則,它也同樣在子元件/指令處可見。

When you provide services in a component, that service is available via the ElementInjector at that component instance. It may also be visible at child component/directives based on visibility rules described in the resolution rules section.

當元件實例被銷燬時,該服務實例也將被銷燬。

When the component instance is destroyed, so is that service instance.

@Directive()@Component()

@Directive() and @Component()

元件是一種特殊型別的指令,這意味著 @Directive() 具有 providers 屬性,@Component() 也同樣如此。 這意味著指令和元件都可以使用 providers 屬性來配置提供者。當使用 providers 屬性為元件或指令配置提供者時,該提供程商就屬於該元件或指令的 ElementInjector。同一元素上的元件和指令共享同一個注入器。

A component is a special type of directive, which means that just as @Directive() has a providers property, @Component() does too. This means that directives as well as components can configure providers, using the providers property. When you configure a provider for a component or directive using the providers property, that provider belongs to the ElementInjector of that component or directive. Components and directives on the same element share an injector.

解析規則

Resolution rules

當為元件/指令解析令牌時,Angular 分為兩個階段來解析它:

When resolving a token for a component/directive, Angular resolves it in two phases:

  1. 針對 ElementInjector 層次結構(其父級)

    Against the ElementInjector hierarchy (its parents)

  2. 針對 ModuleInjector 層次結構(其父級)

    Against the ModuleInjector hierarchy (its parents)

當元件宣告依賴項時,Angular 會嘗試使用它自己的 ElementInjector 來滿足該依賴。 如果元件的注入器缺少提供者,它將把請求傳給其父元件的 ElementInjector

When a component declares a dependency, Angular tries to satisfy that dependency with its own ElementInjector. If the component's injector lacks the provider, it passes the request up to its parent component's ElementInjector.

這些請求將繼續轉發,直到 Angular 找到可以處理該請求的注入器或用完祖先 ElementInjector

The requests keep forwarding up until Angular finds an injector that can handle the request or runs out of ancestor ElementInjectors.

如果 Angular 在任何 ElementInjector 中都找不到提供者,它將返回到發起請求的元素,並在 ModuleInjector 層次結構中進行查詢。如果 Angular 仍然找不到提供者,它將引發錯誤。

If Angular doesn't find the provider in any ElementInjectors, it goes back to the element where the request originated and looks in the ModuleInjector hierarchy. If Angular still doesn't find the provider, it throws an error.

如果你已在不同級別註冊了相同 DI 令牌的提供者,則 Angular 會用遇到的第一個來解析該依賴。例如,如果提供者已經在需要此服務的元件中本地註冊了,則 Angular 不會再尋找同一服務的其它提供者。

If you have registered a provider for the same DI token at different levels, the first one Angular encounters is the one it uses to resolve the dependency. If, for example, a provider is registered locally in the component that needs a service, Angular doesn't look for another provider of the same service.

解析修飾符

Resolution modifiers

可以使用 @Optional()@Self()@SkipSelf()@Host() 來修飾 Angular 的解析行為。從 @angular/core 匯入它們,並在注入服務時在元件類別建構函式中使用它們。

Angular's resolution behavior can be modified with @Optional(), @Self(), @SkipSelf() and @Host(). Import each of them from @angular/core and use each in the component class constructor when you inject your service.

關於展示本節介紹的解析修飾符的可執行應用,請參閱解析修飾符範例解析修飾符範例 / 下載範例

For a working app showcasing the resolution modifiers that this section covers, see theresolution modifiers exampleresolution modifiers example / 下載範例.

修飾符的型別

Types of modifiers

解析修飾符分為三類別:

Resolution modifiers fall into three categories:

  1. 如果 Angular 找不到你要的東西該怎麼辦,用 @Optional()

    What to do if Angular doesn't find what you're looking for, that is @Optional()

  2. 從哪裡開始尋找,用 @SkipSelf()

    Where to start looking, that is @SkipSelf()

  3. 到哪裡停止尋找,用 @Host()@Self()

    Where to stop looking, @Host() and @Self()

預設情況下,Angular 始終從當前的 Injector 開始,並一直向上搜尋。修飾符使你可以更改開始(預設是自己)或結束位置。

By default, Angular always starts at the current Injector and keeps searching all the way up. Modifiers allow you to change the starting (self) or ending location.

另外,你可以組合除 @Host()@Self() 之外的所有修飾符,當然還有 @SkipSelf()@Self()

Additionally, you can combine all of the modifiers except @Host() and @Self() and of course @SkipSelf() and @Self().

@Optional()

@Optional() 允許 Angular 將你注入的服務視為可選服務。這樣,如果無法在執行時解析它,Angular 只會將服務解析為 null,而不會丟擲錯誤。在下面的範例中,服務 OptionalService 沒有在 @NgModule() 或元件類別中提供,所以它沒有在應用中的任何地方。

@Optional() allows Angular to consider a service you inject to be optional. This way, if it can't be resolved at runtime, Angular simply resolves the service as null, rather than throwing an error. In the following example, the service, OptionalService, isn't provided in the service, @NgModule(), or component class, so it isn't available anywhere in the app.

export class OptionalComponent { constructor(@Optional() public optional?: OptionalService) {} }
resolution-modifiers/src/app/optional/optional.component.ts
      
      export class OptionalComponent {
  constructor(@Optional() public optional?: OptionalService) {}
}
    

@Self()

使用 @Self() 讓 Angular 僅檢視當前元件或指令的 ElementInjector

Use @Self() so that Angular will only look at the ElementInjector for the current component or directive.

@Self() 的一個好例子是要注入某個服務,但只有當該服務在當前宿主元素上可用時才行。為了避免這種情況下出錯,請將 @Self()@Optional() 結合使用。

A good use case for @Self() is to inject a service but only if it is available on the current host element. To avoid errors in this situation, combine @Self() with @Optional().

例如,在下面的 SelfComponent 中。請注意在建構函式中注入的 LeafService

For example, in the following SelfComponent, notice the injected LeafService in the constructor.

@Component({ selector: 'app-self-no-data', templateUrl: './self-no-data.component.html', styleUrls: ['./self-no-data.component.css'] }) export class SelfNoDataComponent { constructor(@Self() @Optional() public leaf?: LeafService) { } }
resolution-modifiers/src/app/self-no-data/self-no-data.component.ts
      
      @Component({
  selector: 'app-self-no-data',
  templateUrl: './self-no-data.component.html',
  styleUrls: ['./self-no-data.component.css']
})
export class SelfNoDataComponent {
  constructor(@Self() @Optional() public leaf?: LeafService) { }
}
    

在這個例子中,有一個父提供者,注入服務將返回該值,但是,使用 @Self()@Optional() 注入的服務將返回 null 因為 @Self() 告訴注入器在當前宿主元素上就要停止搜尋。

In this example, there is a parent provider and injecting the service will return the value, however, injecting the service with @Self() and @Optional() will return null because @Self() tells the injector to stop searching in the current host element.

另一個範例顯示了具有 FlowerService 提供者的元件類別。在這個例子中,注入器沒有超出當前 ElementInjector 就停止了,因為它已經找到了 FlowerService 並返回了黃色花朵🌼。

Another example shows the component class with a provider for FlowerService. In this case, the injector looks no further than the current ElementInjector because it finds the FlowerService and returns the yellow flower 🌼.

@Component({ selector: 'app-self', templateUrl: './self.component.html', styleUrls: ['./self.component.css'], providers: [{ provide: FlowerService, useValue: { emoji: '🌼' } }] }) export class SelfComponent { constructor(@Self() public flower: FlowerService) {} }
resolution-modifiers/src/app/self/self.component.ts
      
      @Component({
  selector: 'app-self',
  templateUrl: './self.component.html',
  styleUrls: ['./self.component.css'],
  providers: [{ provide: FlowerService, useValue: { emoji: '🌼' } }]

})
export class SelfComponent {
  constructor(@Self() public flower: FlowerService) {}
}
    

@SkipSelf()

@SkipSelf()@Self() 相反。使用 @SkipSelf(),Angular 在父 ElementInjector 中而不是當前 ElementInjector 中開始搜尋服務。因此,如果父 ElementInjectoremoji 使用了值 🌿(蕨類別),但元件的 providers 陣列中有 🍁(楓葉),則 Angular 將忽略 🍁(楓葉),而使用 🌿(蕨類別)。

@SkipSelf() is the opposite of @Self(). With @SkipSelf(), Angular starts its search for a service in the parent ElementInjector, rather than in the current one. So if the parent ElementInjector were using the value 🌿 (fern) for emoji , but you had 🍁 (maple leaf) in the component's providers array, Angular would ignore 🍁 (maple leaf) and use 🌿 (fern).

要在程式碼中看到這一點,請先假定 emoji 的以下值就是父元件正在使用的值,如本服務所示:

To see this in code, assume that the following value for emoji is what the parent component were using, as in this service:

export class LeafService { emoji = '🌿'; }
resolution-modifiers/src/app/leaf.service.ts
      
      export class LeafService {
  emoji = '🌿';
}
    

想象一下,在子元件中,你有一個不同的值 🍁(楓葉),但你想使用父項的值。你就要使用 @SkipSelf()

Imagine that in the child component, you had a different value, 🍁 (maple leaf) but you wanted to use the parent's value instead. This is when you'd use @SkipSelf():

@Component({ selector: 'app-skipself', templateUrl: './skipself.component.html', styleUrls: ['./skipself.component.css'], // Angular would ignore this LeafService instance providers: [{ provide: LeafService, useValue: { emoji: '🍁' } }] }) export class SkipselfComponent { // Use @SkipSelf() in the constructor constructor(@SkipSelf() public leaf: LeafService) { } }
resolution-modifiers/src/app/skipself/skipself.component.ts
      
      @Component({
  selector: 'app-skipself',
  templateUrl: './skipself.component.html',
  styleUrls: ['./skipself.component.css'],
  // Angular would ignore this LeafService instance
  providers: [{ provide: LeafService, useValue: { emoji: '🍁' } }]
})
export class SkipselfComponent {
  // Use @SkipSelf() in the constructor
  constructor(@SkipSelf() public leaf: LeafService) { }
}
    

在這個例子中,你獲得的 emoji 值將為 🌿(蕨類別),而不是 🍁(楓葉)。

In this case, the value you'd get for emoji would be 🌿 (fern), not 🍁 (maple leaf).

合用 @SkipSelf()@Optional()

@SkipSelf() with @Optional()

如果值為 null 請同時使用 @SkipSelf()@Optional() 來防止錯誤。在下面的範例中,將 Person 服務注入到建構函式中。@SkipSelf() 告訴 Angular 跳過當前的注入器,如果 Person 服務為 null,則 @Optional() 將防止報錯。

Use @SkipSelf() with @Optional() to prevent an error if the value is null. In the following example, the Person service is injected in the constructor. @SkipSelf() tells Angular to skip the current injector and @Optional() will prevent an error should the Person service be null.

class Person { constructor(@Optional() @SkipSelf() parent?: Person) {} }
      
      class Person {
  constructor(@Optional() @SkipSelf() parent?: Person) {}
}
    

@Host()

@Host() 使你可以在搜尋提供者時將當前元件指定為注入器樹的最後一站。即使樹的更上級有一個服務實例,Angular 也不會繼續尋找。使用 @Host() 的例子如下:

@Host() lets you designate a component as the last stop in the injector tree when searching for providers. Even if there is a service instance further up the tree, Angular won't continue looking. Use @Host() as follows:

@Component({ selector: 'app-host', templateUrl: './host.component.html', styleUrls: ['./host.component.css'], // provide the service providers: [{ provide: FlowerService, useValue: { emoji: '🌼' } }] }) export class HostComponent { // use @Host() in the constructor when injecting the service constructor(@Host() @Optional() public flower?: FlowerService) { } }
resolution-modifiers/src/app/host/host.component.ts
      
      @Component({
  selector: 'app-host',
  templateUrl: './host.component.html',
  styleUrls: ['./host.component.css'],
  //  provide the service
  providers: [{ provide: FlowerService, useValue: { emoji: '🌼' } }]
})
export class HostComponent {
  // use @Host() in the constructor when injecting the service
  constructor(@Host() @Optional() public flower?: FlowerService) { }

}
    

由於 HostComponent 在其建構函式中具有 @Host(),因此,無論 HostComponent 的父級是否可能有 flower.emoji 值,該 HostComponent 都將使用 🌼(黃色花朵)。

Since HostComponent has @Host() in its constructor, no matter what the parent of HostComponent might have as a flower.emoji value, the HostComponent will use 🌼 (yellow flower).

範本的邏輯結構

Logical structure of the template

在元件類別中提供服務時,服務在 ElementInjector 樹中的可見性是取決於你在何處以及如何提供這些服務。

When you provide services in the component class, services are visible within the ElementInjector tree relative to where and how you provide those services.

瞭解 Angular 範本的基礎邏輯結構將為你配置服務並進而控制其可見性奠定基礎。

Understanding the underlying logical structure of the Angular template will give you a foundation for configuring services and in turn control their visibility.

元件在範本中使用,如以下範例所示:

Components are used in your templates, as in the following example:

<app-root> <app-child></app-child> </app-root>
      
      <app-root>
    <app-child></app-child>
</app-root>
    

注意:通常,你要在單獨的檔案中宣告元件及其範本。為了理解注入系統的工作原理,從組合邏輯樹的視角來看它們是很有幫助的。使用術語“邏輯”將其與渲染樹(你的應用程式 DOM 樹)區分開。為了標記元件範本的位置,本指南使用 <#VIEW> 偽元素,該元素實際上不存在於渲染樹中,僅用於心智模型中。

Note: Usually, you declare the components and their templates in separate files. For the purposes of understanding how the injection system works, it is useful to look at them from the point of view of a combined logical tree. The term logical distinguishes it from the render tree (your application DOM tree). To mark the locations of where the component templates are located, this guide uses the <#VIEW> pseudo element, which doesn't actually exist in the render tree and is present for mental model purposes only.

下面是如何將 <app-root><app-child> 檢視樹組合為單個邏輯樹的範例:

The following is an example of how the <app-root> and <app-child> view trees are combined into a single logical tree:

<app-root> <#VIEW> <app-child> <#VIEW> ...content goes here... </#VIEW> </app-child> <#VIEW> </app-root>
      
      <app-root>
  <#VIEW>
    <app-child>
     <#VIEW>
       ...content goes here...
     </#VIEW>
    </app-child>
  <#VIEW>
</app-root>
    

當你在元件類別中配置服務時,瞭解這種 <#VIEW> 劃界的思想尤其重要。

Understanding the idea of the <#VIEW> demarcation is especially significant when you configure services in the component class.

@Component() 中提供服務

Providing services in @Component()

你如何透過 @Component() (或 @Directive() )裝飾器提供服務決定了它們的可見性。以下各節示範了 providersviewProviders 以及使用 @SkipSelf()@Host() 修改服務可見性的方法。

How you provide services via an @Component() (or @Directive()) decorator determines their visibility. The following sections demonstrate providers and viewProviders along with ways to modify service visibility with @SkipSelf() and @Host().

元件類別可以透過兩種方式提供服務:

A component class can provide services in two ways:

  1. 使用 providers 陣列

    with a providers array

@Component({ ... providers: [ {provide: FlowerService, useValue: {emoji: '🌺'}} ] })
      
      @Component({
  ...
  providers: [
    {provide: FlowerService, useValue: {emoji: '🌺'}}
  ]
})
    
  1. 使用 viewProviders 陣列

    with a viewProviders array

@Component({ ... viewProviders: [ {provide: AnimalService, useValue: {emoji: '🐶'}} ] })
      
      @Component({
  ...
  viewProviders: [
    {provide: AnimalService, useValue: {emoji: '🐶'}}
  ]
})
    

為了解 providersviewProviders 對服務可見性的影響有何差異,以下各節將逐步建構一個現場演練 / 下載範例並在程式碼和邏輯樹中比較 providersviewProviders 的作用。

To understand how the providers and viewProviders influence service visibility differently, the following sections build a現場演練 / 下載範例step-by-step and compare the use of providers and viewProviders in code and a logical tree.

注意:在邏輯樹中,你會看到 @Provide@Inject@NgModule,這些不是真正的 HTML 屬性,只是為了在這裡證明其幕後的原理。

NOTE: In the logical tree, you'll see @Provide, @Inject, and @NgModule, which are not real HTML attributes but are here to demonstrate what is going on under the hood.

  • @Inject(Token)=>Value 表示,如果要將 Token 注入邏輯樹中的此位置,則它的值為 Value

    @Inject(Token)=>Value demonstrates that if Token is injected at this location in the logical tree its value would be Value.

  • @Provide(Token=Value) 表示,在邏輯樹中的此位置存在一個值為 ValueToken 提供者的宣告。

    @Provide(Token=Value) demonstrates that there is a declaration of Token provider with value Value at this location in the logical tree.

  • @NgModule(Token) 表示,應在此位置使用後備的 NgModule 注入器。

    @NgModule(Token) demonstrates that a fallback NgModule injector should be used at this location.

應用程式結構範例

Example app structure

範例應用程式的 root 提供了 FlowerService,其 emoji 值為 🌺(紅色芙蓉)。

The example app has a FlowerService provided in root with an emoji value of 🌺 (red hibiscus).

@Injectable({ providedIn: 'root' }) export class FlowerService { emoji = '🌺'; }
providers-viewproviders/src/app/flower.service.ts
      
      @Injectable({
  providedIn: 'root'
})
export class FlowerService {
  emoji = '🌺';
}
    

考慮一個只有 AppComponentChildComponent 的簡單應用程式。最基本的渲染檢視看起來就像巢狀的 HTML 元素,例如:

Consider a simple app with only an AppComponent and a ChildComponent. The most basic rendered view would look like nested HTML elements such as the following:

<app-root> <!-- AppComponent selector --> <app-child> <!-- ChildComponent selector --> </app-child> </app-root>
      
      <app-root> <!-- AppComponent selector -->
    <app-child> <!-- ChildComponent selector -->
    </app-child>
</app-root>
    

但是,在幕後,Angular 在解析注入請求時使用如下邏輯視圖表示形式:

However, behind the scenes, Angular uses a logical view representation as follows when resolving injection requests:

<app-root> <!-- AppComponent selector --> <#VIEW> <app-child> <!-- ChildComponent selector --> <#VIEW> </#VIEW> </app-child> </#VIEW> </app-root>
      
      <app-root> <!-- AppComponent selector -->
    <#VIEW>
        <app-child> <!-- ChildComponent selector -->
            <#VIEW>
            </#VIEW>
        </app-child>
    </#VIEW>
</app-root>
    

此處的 <#VIEW> 表示範本的實例。請注意,每個元件都有自己的 <#VIEW>

The <#VIEW> here represents an instance of a template. Notice that each component has its own <#VIEW>.

瞭解此結構可以告知你如何提供和注入服務,並完全控制服務的可見性。

Knowledge of this structure can inform how you provide and inject your services, and give you complete control of service visibility.

現在,考慮 <app-root> 只注入了 FlowerService

Now, consider that <app-root> simply injects the FlowerService:

export class AppComponent { constructor(public flower: FlowerService) {} }
providers-viewproviders/src/app/app.component.ts
      
      export class AppComponent  {
  constructor(public flower: FlowerService) {}
}
    

將繫結新增到 <app-root> 範本來將結果視覺化:

Add a binding to the <app-root> template to visualize the result:

<p>Emoji from FlowerService: {{flower.emoji}}</p>
providers-viewproviders/src/app/app.component.html
      
      <p>Emoji from FlowerService: {{flower.emoji}}</p>
    

該檢視中的輸出為:

The output in the view would be:

Emoji from FlowerService: 🌺
      
      Emoji from FlowerService: 🌺
    

在邏輯樹中,這可以表示成如下形式:

In the logical tree, this would be represented as follows:

<app-root @NgModule(AppModule) @Inject(FlowerService) flower=>"🌺"> <#VIEW> <p>Emoji from FlowerService: {{flower.emoji}} (🌺)</p> <app-child> <#VIEW> </#VIEW> </app-child> </#VIEW> </app-root>
      
      <app-root @NgModule(AppModule)
        @Inject(FlowerService) flower=>"🌺">
  <#VIEW>

    <p>Emoji from FlowerService: {{flower.emoji}} (🌺)</p>

    <app-child>
      <#VIEW>
      </#VIEW>
     </app-child>
  </#VIEW>
</app-root>
    

<app-root> 請求 FlowerService 時,注入器的工作就是解析 FlowerService 令牌。令牌的解析分為兩個階段:

When <app-root> requests the FlowerService, it is the injector's job to resolve the FlowerService token. The resolution of the token happens in two phases:

  1. 注入器確定邏輯樹中搜索的開始位置和結束位置。注入程式從起始位置開始,並在邏輯樹的每個級別上查詢令牌。如果找到令牌,則將其返回。

    The injector determines the starting location in the logical tree and an ending location of the search. The injector begins with the starting location and looks for the token at each level in the logical tree. If the token is found it is returned.

  2. 如果未找到令牌,則注入程式將尋找最接近的父 @NgModule() 委派該請求。

    If the token is not found, the injector looks for the closest parent @NgModule() to delegate the request to.

在這個例子中,約束為:

In the example case, the constraints are:

  1. 從屬於 <app-root><#VIEW> 開始,並結束於 <app-root>

    Start with <#VIEW> belonging to <app-root> and end with <app-root>.

  • 通常,搜尋的起點就是注入點。但是,在這個例子中,<app-root> @Component 的特殊之處在於它們還包括自己的 viewProviders,這就是為什麼搜尋從 <app-root><#VIEW> 開始的原因。(對於匹配同一位置的指令,情況卻並非如此)。

    Normally the starting point for search is at the point of injection. However, in this case <app-root> @Components are special in that they also include their own viewProviders, which is why the search starts at <#VIEW> belonging to <app-root>. (This would not be the case for a directive matched at the same location).

  • 結束位置恰好與元件本身相同,因為它就是此應用程式中最最上層的元件。

    The ending location just happens to be the same as the component itself, because it is the topmost component in this application.

  1. 當在 ElementInjector 中找不到注入令牌時,就用 AppModule 充當後備注入器。

    The AppModule acts as the fallback injector when the injection token can't be found in the ElementInjectors.

使用 providers 陣列

Using the providers array

現在,在 ChildComponent 類別中,為 FlowerService 新增一個提供者,以便在接下來的小節中示範更復雜的解析規則:

Now, in the ChildComponent class, add a provider for FlowerService to demonstrate more complex resolution rules in the upcoming sections:

@Component({ selector: 'app-child', templateUrl: './child.component.html', styleUrls: ['./child.component.css'], // use the providers array to provide a service providers: [{ provide: FlowerService, useValue: { emoji: '🌻' } }] }) export class ChildComponent { // inject the service constructor( public flower: FlowerService) { } }
providers-viewproviders/src/app/child.component.ts
      
      @Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css'],
  // use the providers array to provide a service
  providers: [{ provide: FlowerService, useValue: { emoji: '🌻' } }]
})

export class ChildComponent {
  // inject the service
  constructor( public flower: FlowerService) { }
}
    

現在,在 @Component() 裝飾器中提供了 FlowerService,當 <app-child> 請求該服務時,注入器僅需要查詢 <app-child> 自己的 ElementInjector。不必再透過注入器樹繼續搜尋。

Now that the FlowerService is provided in the @Component() decorator, when the <app-child> requests the service, the injector has only to look as far as the <app-child>'s own ElementInjector. It won't have to continue the search any further through the injector tree.

下一步是將繫結新增到 ChildComponent 範本。

The next step is to add a binding to the ChildComponent template.

<p>Emoji from FlowerService: {{flower.emoji}}</p>
providers-viewproviders/src/app/child.component.html
      
      <p>Emoji from FlowerService: {{flower.emoji}}</p>
    

要渲染新的值,請在 AppComponent 範本的底部新增 <app-child>,以便其檢視也顯示向日葵:

To render the new values, add <app-child> to the bottom of the AppComponent template so the view also displays the sunflower:

Child Component Emoji from FlowerService: 🌻
      
      Child Component
Emoji from FlowerService: 🌻
    

在邏輯樹中,可以把它表示成這樣:

In the logical tree, this would be represented as follows:

<app-root @NgModule(AppModule) @Inject(FlowerService) flower=>"🌺"> <#VIEW> <p>Emoji from FlowerService: {{flower.emoji}} (🌺)</p> <app-child @Provide(FlowerService="🌻") @Inject(FlowerService)=>"🌻"> <!-- search ends here --> <#VIEW> <!-- search starts here --> <h2>Parent Component</h2> <p>Emoji from FlowerService: {{flower.emoji}} (🌻)</p> </#VIEW> </app-child> </#VIEW> </app-root>
      
      <app-root @NgModule(AppModule)
        @Inject(FlowerService) flower=>"🌺">
  <#VIEW>

    <p>Emoji from FlowerService: {{flower.emoji}} (🌺)</p>

    <app-child @Provide(FlowerService="🌻")
               @Inject(FlowerService)=>"🌻"> <!-- search ends here -->
      <#VIEW> <!-- search starts here -->

        <h2>Parent Component</h2>

        <p>Emoji from FlowerService: {{flower.emoji}} (🌻)</p>

      </#VIEW>
     </app-child>
  </#VIEW>
</app-root>
    

<app-child> 請求 FlowerService 時,注入器從 <app-child><#VIEW> 開始搜尋(包括 <#VIEW>,因為它是從 @Component() 注入的),併到 <app-child> 結束。在這個例子中,FlowerService<app-child>providers 陣列中解析為向日葵🌻。注入器不必在注入器樹中進一步查詢。一旦找到 FlowerService,它便停止執行,再也看不到🌺(紅芙蓉)。

When <app-child> requests the FlowerService, the injector begins its search at the <#VIEW> belonging to <app-child> (<#VIEW> is included because it is injected from @Component()) and ends with <app-child>. In this case, the FlowerService is resolved in the <app-child>'s providers array with sunflower 🌻. The injector doesn't have to look any further in the injector tree. It stops as soon as it finds the FlowerService and never sees the 🌺 (red hibiscus).

使用 viewProviders 陣列

Using the viewProviders array

使用 viewProviders 陣列是在 @Component() 裝飾器中提供服務的另一種方法。使用 viewProviders 使服務在 <#VIEW> 中可見。

Use the viewProviders array as another way to provide services in the @Component() decorator. Using viewProviders makes services visible in the <#VIEW>.

除了使用 viewProviders 陣列外,其它步驟與使用 providers 陣列相同。

The steps are the same as using the providers array, with the exception of using the viewProviders array instead.

關於這些步驟的說明,請繼續本節。如果你可以自行設定,請跳至修改服務可用性 一節。

For step-by-step instructions, continue with this section. If you can set it up on your own, skip ahead to Modifying service availability.

該範例應用程式具有第二個服務 AnimalService 來示範 viewProviders

The example app features a second service, the AnimalService to demonstrate viewProviders.

首先,建立一個 AnimalServiceemoji 的🐳(鯨魚)屬性:

First, create an AnimalService with an emoji property of 🐳 (whale):

import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class AnimalService { emoji = '🐳'; }
providers-viewproviders/src/app/animal.service.ts
      
      import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class AnimalService {
  emoji = '🐳';
}
    

遵循與 FlowerService 相同的模式,將 AnimalService 注入 AppComponent 類別:

Following the same pattern as with the FlowerService, inject the AnimalService in the AppComponent class:

export class AppComponent { constructor(public flower: FlowerService, public animal: AnimalService) {} }
providers-viewproviders/src/app/app.component.ts
      
      export class AppComponent  {
  constructor(public flower: FlowerService, public animal: AnimalService) {}
}
    

注意:你可以保留所有與 FlowerService 相關的程式碼,因為它可以與 AnimalService 進行比較。

Note: You can leave all the FlowerService related code in place as it will allow a comparison with the AnimalService.

新增一個 viewProviders 陣列,並將 AnimalService 也注入到 <app-child> 類別中,但是給 emoji 一個不同的值。在這裡,它的值為🐶(小狗)。

Add a viewProviders array and inject the AnimalService in the <app-child> class, too, but give emoji a different value. Here, it has a value of 🐶 (puppy).

@Component({ selector: 'app-child', templateUrl: './child.component.html', styleUrls: ['./child.component.css'], // provide services providers: [{ provide: FlowerService, useValue: { emoji: '🌻' } }], viewProviders: [{ provide: AnimalService, useValue: { emoji: '🐶' } }] }) export class ChildComponent { // inject service constructor( public flower: FlowerService, public animal: AnimalService) { } }
providers-viewproviders/src/app/child.component.ts
      
      @Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css'],
  // provide services
  providers: [{ provide: FlowerService, useValue: { emoji: '🌻' } }],
  viewProviders: [{ provide: AnimalService, useValue: { emoji: '🐶' } }]
})

export class ChildComponent {
  // inject service
  constructor( public flower: FlowerService, public animal: AnimalService) { }
}
    

將繫結新增到 ChildComponentAppComponent 範本。在 ChildComponent 範本中,新增以下繫結:

Add bindings to the ChildComponent and the AppComponent templates. In the ChildComponent template, add the following binding:

<p>Emoji from AnimalService: {{animal.emoji}}</p>
providers-viewproviders/src/app/child.component.html
      
      <p>Emoji from AnimalService: {{animal.emoji}}</p>
    

此外,將其新增到 AppComponent 範本:

Additionally, add the same to the AppComponent template:

<p>Emoji from AnimalService: {{animal.emoji}}</p>
providers-viewproviders/src/app/app.component.html
      
      <p>Emoji from AnimalService: {{animal.emoji}}</p>
    

現在,你應該在瀏覽器中看到兩個值:

Now you should see both values in the browser:

AppComponent Emoji from AnimalService: 🐳 Child Component Emoji from AnimalService: 🐶
      
      AppComponent
Emoji from AnimalService: 🐳

Child Component
Emoji from AnimalService: 🐶
    

viewProviders 範例的邏輯樹如下:

The logic tree for this example of viewProviders is as follows:

<app-root @NgModule(AppModule) @Inject(AnimalService) animal=>"🐳"> <#VIEW> <app-child> <#VIEW @Provide(AnimalService="🐶") @Inject(AnimalService=>"🐶")> <!-- ^^using viewProviders means AnimalService is available in <#VIEW>--> <p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p> </#VIEW> </app-child> </#VIEW> </app-root>
      
      <app-root @NgModule(AppModule)
        @Inject(AnimalService) animal=>"🐳">
  <#VIEW>
    <app-child>
      <#VIEW
       @Provide(AnimalService="🐶")
       @Inject(AnimalService=>"🐶")>

       <!-- ^^using viewProviders means AnimalService is available in <#VIEW>-->

       <p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p>

      </#VIEW>
     </app-child>
  </#VIEW>
</app-root>
    

FlowerService 範例一樣,<app-child> @Component() 裝飾器中提供了 AnimalService。這意味著,由於注入器首先在元件的 ElementInjector 中查詢,因此它將找到 AnimalService 的值 🐶(小狗)。它不需要繼續搜尋 ElementInjector 樹,也不需要搜尋 ModuleInjector

Just as with the FlowerService example, the AnimalService is provided in the <app-child> @Component() decorator. This means that since the injector first looks in the ElementInjector of the component, it finds the AnimalService value of 🐶 (puppy). It doesn't need to continue searching the ElementInjector tree, nor does it need to search the ModuleInjector.

providersviewProviders

providers vs. viewProviders

為了看清 providersviewProviders 的差異,請在範例中新增另一個元件,並將其命名為 InspectorComponentInspectorComponent 將是 ChildComponent 的子 ChildComponent。在 inspector.component.ts 中,將 FlowerServiceAnimalService 注入建構函式中:

To see the difference between using providers and viewProviders, add another component to the example and call it InspectorComponent. InspectorComponent will be a child of the ChildComponent. In inspector.component.ts, inject the FlowerService and AnimalService in the constructor:

export class InspectorComponent { constructor(public flower: FlowerService, public animal: AnimalService) { } }
providers-viewproviders/src/app/inspector/inspector.component.ts
      
      export class InspectorComponent {
  constructor(public flower: FlowerService, public animal: AnimalService) { }
}
    

你不需要 providersviewProviders 陣列。接下來,在 inspector.component.html 中,從以前的元件中新增相同的 html:

You do not need a providers or viewProviders array. Next, in inspector.component.html, add the same markup from previous components:

<p>Emoji from FlowerService: {{flower.emoji}}</p> <p>Emoji from AnimalService: {{animal.emoji}}</p>
providers-viewproviders/src/app/inspector/inspector.component.html
      
      <p>Emoji from FlowerService: {{flower.emoji}}</p>
<p>Emoji from AnimalService: {{animal.emoji}}</p>
    

別忘了將 InspectorComponent 新增到 AppModule declarations 陣列。

Remember to add the InspectorComponent to the AppModule declarations array.

@NgModule({ imports: [ BrowserModule, FormsModule ], declarations: [ AppComponent, ChildComponent, InspectorComponent ], bootstrap: [ AppComponent ], providers: [] }) export class AppModule { }
providers-viewproviders/src/app/app.module.ts
      
      @NgModule({
  imports:      [ BrowserModule, FormsModule ],
  declarations: [ AppComponent, ChildComponent, InspectorComponent ],
  bootstrap:    [ AppComponent ],
  providers: []
})
export class AppModule { }
    

接下來,確保你的 child.component.html 包含以下內容:

Next, make sure your child.component.html contains the following:

<p>Emoji from FlowerService: {{flower.emoji}}</p> <p>Emoji from AnimalService: {{animal.emoji}}</p> <div class="container"> <h3>Content projection</h3> <ng-content></ng-content> </div> <h3>Inside the view</h3> <app-inspector></app-inspector>
providers-viewproviders/src/app/child/child.component.html
      
      <p>Emoji from FlowerService: {{flower.emoji}}</p>
<p>Emoji from AnimalService: {{animal.emoji}}</p>

<div class="container">
  <h3>Content projection</h3>
	<ng-content></ng-content>
</div>

<h3>Inside the view</h3>
<app-inspector></app-inspector>
    

前兩行帶有繫結,來自之前的步驟。新的部分是 <ng-content><app-inspector><ng-content> 允許你投影內容,ChildComponent 範本中的 <app-inspector> 使 InspectorComponent 成為 ChildComponent 的子元件。

The first two lines, with the bindings, are there from previous steps. The new parts are <ng-content> and <app-inspector>. <ng-content> allows you to project content, and <app-inspector> inside the ChildComponent template makes the InspectorComponent a child component of ChildComponent.

接下來,將以下內容新增到 app.component.html 中以利用內容投影的優勢。

Next, add the following to app.component.html to take advantage of content projection.

<app-child><app-inspector></app-inspector></app-child>
providers-viewproviders/src/app/app.component.html
      
      <app-child><app-inspector></app-inspector></app-child>
    

現在,瀏覽器將渲染以下內容,為簡潔起見,省略了前面的範例:

The browser now renders the following, omitting the previous examples for brevity:

//...Omitting previous examples. The following applies to this section. Content projection: This is coming from content. Doesn't get to see puppy because the puppy is declared inside the view only. Emoji from FlowerService: 🌻 Emoji from AnimalService: 🐳 Emoji from FlowerService: 🌻 Emoji from AnimalService: 🐶
      
      //...Omitting previous examples. The following applies to this section.

Content projection: This is coming from content. Doesn't get to see
puppy because the puppy is declared inside the view only.

Emoji from FlowerService: 🌻
Emoji from AnimalService: 🐳

Emoji from FlowerService: 🌻
Emoji from AnimalService: 🐶
    

這四個繫結說明了 providersviewProviders 之間的區別。由於🐶(小狗)在<#VIEW>中宣告,因此投影內容不可見。投影的內容中會看到🐳(鯨魚)。

These four bindings demonstrate the difference between providers and viewProviders. Since the 🐶 (puppy) is declared inside the <#VIEW>, it isn't visible to the projected content. Instead, the projected content sees the 🐳 (whale).

但是下一部分,InspectorComponentChildComponent 的子元件,InspectorComponent<#VIEW> 內部,因此當它請求 AnimalService 時,它會看到🐶(小狗)。

The next section though, where InspectorComponent is a child component of ChildComponent, InspectorComponent is inside the <#VIEW>, so when it asks for the AnimalService, it sees the 🐶 (puppy).

邏輯樹中的 AnimalService 如下所示:

The AnimalService in the logical tree would look like this:

<app-root @NgModule(AppModule) @Inject(AnimalService) animal=>"🐳"> <#VIEW> <app-child> <#VIEW @Provide(AnimalService="🐶") @Inject(AnimalService=>"🐶")> <!-- ^^using viewProviders means AnimalService is available in <#VIEW>--> <p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p> <app-inspector> <p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p> </app-inspector> </#VIEW> <app-inspector> <#VIEW> <p>Emoji from AnimalService: {{animal.emoji}} (🐳)</p> </#VIEW> </app-inspector> </app-child> </#VIEW> </app-root>
      
      <app-root @NgModule(AppModule)
        @Inject(AnimalService) animal=>"🐳">
  <#VIEW>
    <app-child>
      <#VIEW
       @Provide(AnimalService="🐶")
       @Inject(AnimalService=>"🐶")>

       <!-- ^^using viewProviders means AnimalService is available in <#VIEW>-->

       <p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p>

       <app-inspector>

        <p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p>

       </app-inspector>
      </#VIEW>
      <app-inspector>
        <#VIEW>

          <p>Emoji from AnimalService: {{animal.emoji}} (🐳)</p>

        </#VIEW>
      </app-inspector>
     </app-child>
  </#VIEW>
</app-root>
    

<app-inspector> 的投影內容中看到了🐳(鯨魚),而不是🐶(小狗),因為🐶(小狗)在 <app-child><#VIEW> 中。如果 <app-inspector> 也位於 <#VIEW> 則只能看到🐶(小狗)。

The projected content of <app-inspector> sees the 🐳 (whale), not the 🐶 (puppy), because the 🐶 (puppy) is inside the <app-child> <#VIEW>. The <app-inspector> can only see the 🐶 (puppy) if it is also within the <#VIEW>.

修改服務可見性

Modifying service visibility

本節講的是如何使用可見性修飾符 @Host()@Self()@SkipSelf() 來限制 ElementInjector 的開始和結束範圍。

This section describes how to limit the scope of the beginning and ending ElementInjector using the visibility decorators @Host(), @Self(), and @SkipSelf().

提供者令牌的可見性

Visibility of provided tokens

可見性裝飾器影響搜尋注入令牌時在邏輯樹中開始和結束的位置。為此,要將可見性裝飾器放置在注入點,即 constructor(),而不是在宣告點。

Visibility decorators influence where the search for the injection token begins and ends in the logic tree. To do this, place visibility decorators at the point of injection, that is, the constructor(), rather than at a point of declaration.

為了修改該注入器從哪裡開始尋找 FlowerService,把 @SkipSelf() 加到 <app-child>@Inject 宣告 FlowerService 中。該宣告在 <app-child> 建構函式中,如 child.component.ts 所示:

To alter where the injector starts looking for FlowerService, add @SkipSelf() to the <app-child> @Inject declaration for the FlowerService. This declaration is in the <app-child> constructor as shown in child.component.ts:

constructor(@SkipSelf() public flower : FlowerService) { }
      
      constructor(@SkipSelf() public flower : FlowerService) { }
    

使用 @SkipSelf()<app-child> 注入器不會尋找自身來獲取 FlowerService。相反,噴射器開始在 <app-root>ElementInjector 中尋找 FlowerService,在那裡它什麼也沒找到。 然後,它返回到 <app-child>ModuleInjector 並找到🌺(紅芙蓉)值,這是可用的,因為 <app-child> ModuleInjector<app-root> ModuleInjector 被展開成了一個 ModuleInjector。因此,UI 將渲染以下內容:

With @SkipSelf(), the <app-child> injector doesn't look to itself for the FlowerService. Instead, the injector starts looking for the FlowerService at the <app-root>'s ElementInjector, where it finds nothing. Then, it goes back to the <app-child> ModuleInjector and finds the 🌺 (red hibiscus) value, which is available because the <app-child> ModuleInjector and the <app-root> ModuleInjector are flattened into one ModuleInjector. Thus, the UI renders the following:

Emoji from FlowerService: 🌺
      
      Emoji from FlowerService: 🌺
    

在邏輯樹中,這種情況可能如下所示:

In a logical tree, this same idea might look like this:

<app-root @NgModule(AppModule) @Inject(FlowerService) flower=>"🌺"> <#VIEW> <app-child @Provide(FlowerService="🌻")> <#VIEW @Inject(FlowerService, SkipSelf)=>"🌺"> <!-- With SkipSelf, the injector looks to the next injector up the tree --> </#VIEW> </app-child> </#VIEW> </app-root>
      
      <app-root @NgModule(AppModule)
        @Inject(FlowerService) flower=>"🌺">
  <#VIEW>
    <app-child @Provide(FlowerService="🌻")>
      <#VIEW @Inject(FlowerService, SkipSelf)=>"🌺">

      <!-- With SkipSelf, the injector looks to the next injector up the tree -->

      </#VIEW>
      </app-child>
  </#VIEW>
</app-root>
    

儘管 <app-child> 提供了🌻(向日葵),但該應用程式渲染了🌺(紅色芙蓉),因為 @SkipSelf() 導致當前的注入器跳過了自身並尋找其父級。

Though <app-child> provides the 🌻 (sunflower), the app renders the 🌺 (red hibiscus) because @SkipSelf() causes the current injector to skip itself and look to its parent.

如果現在將 @Host()(以及 @SkipSelf() )新增到了 FlowerService@Inject,其結果將為 null。這是因為 @Host() 將搜尋的上限限制為 <#VIEW>。這是在邏輯樹中的情況:

If you now add @Host() (in addition to the @SkipSelf()) to the @Inject of the FlowerService, the result will be null. This is because @Host() limits the upper bound of the search to the <#VIEW>. Here's the idea in the logical tree:

<app-root @NgModule(AppModule) @Inject(FlowerService) flower=>"🌺"> <#VIEW> <!-- end search here with null--> <app-child @Provide(FlowerService="🌻")> <!-- start search here --> <#VIEW @Inject(FlowerService, @SkipSelf, @Host, @Optional)=>null> </#VIEW> </app-parent> </#VIEW> </app-root>
      
      <app-root @NgModule(AppModule)
        @Inject(FlowerService) flower=>"🌺">
  <#VIEW> <!-- end search here with null-->
    <app-child @Provide(FlowerService="🌻")> <!-- start search here -->
      <#VIEW @Inject(FlowerService, @SkipSelf, @Host, @Optional)=>null>
      </#VIEW>
      </app-parent>
  </#VIEW>
</app-root>
    

在這裡,服務及其值是相同的,但是 @Host() 阻止了注入器對 FlowerService 進行任何高於 <#VIEW> 的查詢,因此找不到它並返回 null

Here, the services and their values are the same, but @Host() stops the injector from looking any further than the <#VIEW> for FlowerService, so it doesn't find it and returns null.

注意:範例應用程式使用 @Optional() 因此該應用程式不會引發錯誤,但是其原理是一樣的。

Note: The example app uses @Optional() so the app does not throw an error, but the principles are the same.

@SkipSelf()viewProviders

@SkipSelf() and viewProviders

<app-child> 目前提供在 viewProviders 陣列中提供了值為 🐶(小狗)的 AnimalService。由於注入器只需要檢視 <app-child>ElementInjector 中的 AnimalService,它就不會看到🐳(鯨魚)。

The <app-child> currently provides the AnimalService in the viewProviders array with the value of 🐶 (puppy). Because the injector has only to look at the <app-child>'s ElementInjector for the AnimalService, it never sees the 🐳 (whale).

就像在 FlowerService 範例中一樣,如果將 @SkipSelf() 新增到 AnimalService 的建構函式中,則注入器將不在 AnimalService 的當前 <app-child>ElementInjector 中查詢 AnimalService

Just as in the FlowerService example, if you add @SkipSelf() to the constructor for the AnimalService, the injector won't look in the current <app-child>'s ElementInjector for the AnimalService.

export class ChildComponent { // add @SkipSelf() constructor(@SkipSelf() public animal : AnimalService) { } }
      
      export class ChildComponent {

// add @SkipSelf()
  constructor(@SkipSelf() public animal : AnimalService) { }

}
    

相反,注入器將從 <app-root> ElementInjector 開始找。請記住,<app-child> 類別在 viewProviders 陣列中 AnimalService 中提供了🐶(小狗)的值:

Instead, the injector will begin at the <app-root> ElementInjector. Remember that the <app-child> class provides the AnimalService in the viewProviders array with a value of 🐶 (puppy):

@Component({ selector: 'app-child', ... viewProviders: [{ provide: AnimalService, useValue: { emoji: '🐶' } }] })
      
      @Component({
  selector: 'app-child',
  ...
  viewProviders:
  [{ provide: AnimalService, useValue: { emoji: '🐶' } }]
})
    

<app-child> 中使用 @SkipSelf() 的邏輯樹是這樣的:

The logical tree looks like this with @SkipSelf() in <app-child>:

<app-root @NgModule(AppModule) @Inject(AnimalService=>"🐳")> <#VIEW><!-- search begins here --> <app-child> <#VIEW @Provide(AnimalService="🐶") @Inject(AnimalService, SkipSelf=>"🐳")> <!--Add @SkipSelf --> </#VIEW> </app-child> </#VIEW> </app-root>
      
      <app-root @NgModule(AppModule)
        @Inject(AnimalService=>"🐳")>
  <#VIEW><!-- search begins here -->
    <app-child>
      <#VIEW
       @Provide(AnimalService="🐶")
       @Inject(AnimalService, SkipSelf=>"🐳")>

       <!--Add @SkipSelf -->

      </#VIEW>
      </app-child>
  </#VIEW>
</app-root>
    

<app-child> 中使用 @SkipSelf(),注入器就會在 <app-root>ElementInjector 中找到 🐳(鯨)。

With @SkipSelf() in the <app-child>, the injector begins its search for the AnimalService in the <app-root> ElementInjector and finds 🐳 (whale).

@Host()viewProviders

@Host() and viewProviders

如果把 @Host() 新增到 AnimalService 的建構函式上,結果就是🐶(小狗),因為注入器會在 <app-child><#VIEW> 中查詢 AnimalService 服務。這裡是 <app-child> 類別中的 viewProviders 陣列和建構函式中的 @Host()

If you add @Host() to the constructor for AnimalService, the result is 🐶 (puppy) because the injector finds the AnimalService in the <app-child> <#VIEW>. Here is the viewProviders array in the <app-child> class and @Host() in the constructor:

@Component({ selector: 'app-child', ... viewProviders: [{ provide: AnimalService, useValue: { emoji: '🐶' } }] }) export class ChildComponent { constructor(@Host() public animal : AnimalService) { } }
      
      @Component({
  selector: 'app-child',
  ...
  viewProviders:
  [{ provide: AnimalService, useValue: { emoji: '🐶' } }]

})
export class ChildComponent {
  constructor(@Host() public animal : AnimalService) { }
}
    

@Host() 導致注入器開始查詢,直到遇到 <#VIEW> 的邊緣。

@Host() causes the injector to look until it encounters the edge of the <#VIEW>.

<app-root @NgModule(AppModule) @Inject(AnimalService=>"🐳")> <#VIEW> <app-child> <#VIEW @Provide(AnimalService="🐶") @Inject(AnimalService, @Host=>"🐶")> <!-- @Host stops search here --> </#VIEW> </app-child> </#VIEW> </app-root>
      
      <app-root @NgModule(AppModule)
        @Inject(AnimalService=>"🐳")>
  <#VIEW>
    <app-child>
      <#VIEW
       @Provide(AnimalService="🐶")
       @Inject(AnimalService, @Host=>"🐶")> <!-- @Host stops search here -->
      </#VIEW>
      </app-child>
  </#VIEW>
</app-root>
    

將帶有第三個動物🦔(刺蝟)的 viewProviders 陣列新增到 app.component.ts@Component() 元資料中:

Add a viewProviders array with a third animal, 🦔 (hedgehog), to the app.component.ts @Component() metadata:

@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: [ './app.component.css' ], viewProviders: [{ provide: AnimalService, useValue: { emoji: '🦔' } }] })
      
      @Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ],
  viewProviders: [{ provide: AnimalService, useValue: { emoji: '🦔' } }]
})
    

接下來,同時把 @SkipSelf()@Host() 加在 child.component.tsAnimalService 的建構函式中。這是 <app-child> 建構函式中的 @Host()@SkipSelf()

Next, add @SkipSelf() along with @Host() to the constructor for the Animal Service in child.component.ts. Here are @Host() and @SkipSelf() in the <app-child> constructor :

export class ChildComponent { constructor( @Host() @SkipSelf() public animal : AnimalService) { } }
      
      export class ChildComponent {

  constructor(
  @Host() @SkipSelf() public animal : AnimalService) { }

}
    

@Host()SkipSelf() 應用於 providers 陣列中的 FlowerService,結果為 null,因為 @SkipSelf() 會在 <app-child> 的注入器中開始搜尋,但是 @Host() 要求它在 <#VIEW> 停止搜尋 —— 沒有 FlowerService。在邏輯樹中,你可以看到 FlowerService<app-child> 中可見,而在 <#VIEW> 中不可見。

When @Host() and SkipSelf() were applied to the FlowerService, which is in the providers array, the result was null because @SkipSelf() starts its search in the <app-child> injector, but @Host() stops searching at <#VIEW>—where there is no FlowerService. In the logical tree, you can see that the FlowerService is visible in <app-child>, not its <#VIEW>.

不過,提供在 AppComponentviewProviders 陣列中的 AnimalService,是可見的。

However, the AnimalService, which is provided in the AppComponent viewProviders array, is visible.

邏輯樹表示法說明了為何如此:

The logical tree representation shows why this is:

<app-root @NgModule(AppModule) @Inject(AnimalService=>"🐳")> <#VIEW @Provide(AnimalService="🦔") @Inject(AnimalService, @SkipSelf, @Host, @Optional)=>"🦔"> <!-- ^^@SkipSelf() starts here, @Host() stops here^^ --> <app-child> <#VIEW @Provide(AnimalService="🐶") @Inject(AnimalService, @SkipSelf, @Host, @Optional)=>"🐶"> <!-- Add @SkipSelf ^^--> </#VIEW> </app-child> </#VIEW> </app-root>
      
      <app-root @NgModule(AppModule)
        @Inject(AnimalService=>"🐳")>
  <#VIEW @Provide(AnimalService="🦔")
         @Inject(AnimalService, @SkipSelf, @Host, @Optional)=>"🦔">

    <!-- ^^@SkipSelf() starts here,  @Host() stops here^^ -->

    <app-child>
      <#VIEW @Provide(AnimalService="🐶")
             @Inject(AnimalService, @SkipSelf, @Host, @Optional)=>"🐶">

               <!-- Add @SkipSelf ^^-->

      </#VIEW>
      </app-child>
  </#VIEW>
</app-root>
    

@SkipSelf() 導致注入器從 <app-root> 而不是 <app-child> 處開始對 AnimalService 進行搜尋,而 @Host() 會在 <app-root><#VIEW> 處停止搜尋。 由於 AnimalService 是透過 viewProviders 陣列提供的,因此注入程式會在 <#VIEW> 找到🦔(刺蝟)。

@SkipSelf(), causes the injector to start its search for the AnimalService at the <app-root>, not the <app-child>, where the request originates, and @Host() stops the search at the <app-root> <#VIEW>. Since AnimalService is provided via the viewProviders array, the injector finds 🦔 (hedgehog) in the <#VIEW>.

ElementInjector 用例範例

ElementInjector use case examples

在不同級別配置一個或多個提供者的能力開闢了很有用的可能性。要檢視正在執行的應用中的以下情況,請參閱英雄範例英雄範例 / 下載範例

The ability to configure one or more providers at different levels opens up useful possibilities. For a look at the following scenarios in a working app, see theheroes use case examplesheroes use case examples / 下載範例.

場景:服務隔離

Scenario: service isolation

出於架構方面的考慮,可能會讓你決定把一個服務限制到只能在它所屬的那個應用域中訪問。 比如,這個例子中包括一個用於顯示反派列表的 VillainsListComponent,它會從 VillainsService 中獲得反派列表資料。

Architectural reasons may lead you to restrict access to a service to the application domain where it belongs. For example, the guide sample includes a VillainsListComponent that displays a list of villains. It gets those villains from a VillainsService.

如果你在根模組 AppModule 中(也就是你註冊 HeroesService 的地方)提供 VillainsService,就會讓應用中的任何地方都能訪問到 VillainsService,包括針對英雄的工作流。如果你稍後修改了 VillainsService,就可能破壞了英雄元件中的某些地方。在根模組 AppModule 中提供該服務將會引入此風險。

If you provided VillainsService in the root AppModule (where you registered the HeroesService), that would make the VillainsService visible everywhere in the application, including the Hero workflows. If you later modified the VillainsService, you could break something in a hero component somewhere.

該怎麼做呢?你可以在 VillainsListComponentproviders 元資料中提供 VillainsService,就像這樣:

Instead, you can provide the VillainsService in the providers metadata of the VillainsListComponent like this:

@Component({ selector: 'app-villains-list', templateUrl: './villains-list.component.html', providers: [ VillainsService ] })
src/app/villains-list.component.ts (metadata)
      
      @Component({
  selector: 'app-villains-list',
  templateUrl: './villains-list.component.html',
  providers: [ VillainsService ]
})
    

VillainsListComponent 的元資料中而不是其它地方提供 VillainsService 服務,該服務就會只在 VillainsListComponent 及其子元件樹中可用。

By providing VillainsService in the VillainsListComponent metadata and nowhere else, the service becomes available only in the VillainsListComponent and its sub-component tree.

VillainService 對於 VillainsListComponent 來說是單例的,因為它就是在這裡宣告的。只要 VillainsListComponent 沒有銷燬,它就始終是 VillainService 的同一個實例。但是對於 VillainsListComponent 的多個實例,每個 VillainsListComponent 的實例都會有自己的 VillainService 實例。

VillainService is a singleton with respect to VillainsListComponent because that is where it is declared. As long as VillainsListComponent does not get destroyed it will be the same instance of VillainService but if there are multilple instances of VillainsListComponent, then each instance of VillainsListComponent will have its own instance of VillainService.

場景:多重編輯會話

Scenario: multiple edit sessions

很多應用允許使用者同時進行多個任務。 比如,在納稅申報應用中,申報人可以開啟多個報稅單,隨時可能從一個切換到另一個。

Many applications allow users to work on several open tasks at the same time. For example, in a tax preparation application, the preparer could be working on several tax returns, switching from one to the other throughout the day.

本章要示範的場景仍然是基於《英雄之旅》的。 想象一個外層的 HeroListComponent,它顯示一個超級英雄的列表。

This guide demonstrates that scenario with an example in the Tour of Heroes theme. Imagine an outer HeroListComponent that displays a list of super heroes.

要開啟一個英雄的報稅單,申報者點選英雄名,它就會開啟一個元件來編輯那個申報單。 每個選中的申報單都會在自己的元件中開啟,並且可以同時開啟多個申報單。

To open a hero's tax return, the preparer clicks on a hero name, which opens a component for editing that return. Each selected hero tax return opens in its own component and multiple returns can be open at the same time.

每個報稅單元件都有下列特徵:

Each tax return component has the following characteristics:

  • 屬於它自己的報稅單會話。

    Is its own tax return editing session.

  • 可以修改一個報稅單,而不會影響另一個元件中的申報單。

    Can change a tax return without affecting a return in another component.

  • 能把所做的修改儲存到它的報稅單中,或者放棄它們。

    Has the ability to save the changes to its tax return or cancel them.

假設 HeroTaxReturnComponent 還有一些管理並還原這些更改的邏輯。 這對於簡單的報稅單來說是很容易的。 不過,在現實世界中,報稅單的資料模型非常複雜,對這些修改的管理可能不得不投機取巧。 你可以把這種管理任務委託給一個輔助服務,就像這個例子中所做的。

Suppose that the HeroTaxReturnComponent had logic to manage and restore changes. That would be a pretty easy task for a simple hero tax return. In the real world, with a rich tax return data model, the change management would be tricky. You could delegate that management to a helper service, as this example does.

報稅單服務 HeroTaxReturnService 快取了單條 HeroTaxReturn,用於追蹤那個申報單的變更,並且可以儲存或還原它。 它還委託給了全應用級的單例服務 HeroService,它是透過依賴注入機制取得的。

The HeroTaxReturnService caches a single HeroTaxReturn, tracks changes to that return, and can save or restore it. It also delegates to the application-wide singleton HeroService, which it gets by injection.

import { Injectable } from '@angular/core'; import { HeroTaxReturn } from './hero'; import { HeroesService } from './heroes.service'; @Injectable() export class HeroTaxReturnService { private currentTaxReturn: HeroTaxReturn; private originalTaxReturn: HeroTaxReturn; constructor(private heroService: HeroesService) { } set taxReturn(htr: HeroTaxReturn) { this.originalTaxReturn = htr; this.currentTaxReturn = htr.clone(); } get taxReturn(): HeroTaxReturn { return this.currentTaxReturn; } restoreTaxReturn() { this.taxReturn = this.originalTaxReturn; } saveTaxReturn() { this.taxReturn = this.currentTaxReturn; this.heroService.saveTaxReturn(this.currentTaxReturn).subscribe(); } }
src/app/hero-tax-return.service.ts
      
      import { Injectable } from '@angular/core';
import { HeroTaxReturn } from './hero';
import { HeroesService } from './heroes.service';

@Injectable()
export class HeroTaxReturnService {
  private currentTaxReturn: HeroTaxReturn;
  private originalTaxReturn: HeroTaxReturn;

  constructor(private heroService: HeroesService) { }

  set taxReturn(htr: HeroTaxReturn) {
    this.originalTaxReturn = htr;
    this.currentTaxReturn  = htr.clone();
  }

  get taxReturn(): HeroTaxReturn {
    return this.currentTaxReturn;
  }

  restoreTaxReturn() {
    this.taxReturn = this.originalTaxReturn;
  }

  saveTaxReturn() {
    this.taxReturn = this.currentTaxReturn;
    this.heroService.saveTaxReturn(this.currentTaxReturn).subscribe();
  }
}
    

下面是正在使用 HeroTaxReturnServiceHeroTaxReturnComponent 元件。

Here is the HeroTaxReturnComponent that makes use of HeroTaxReturnService.

import { Component, EventEmitter, Input, Output } from '@angular/core'; import { HeroTaxReturn } from './hero'; import { HeroTaxReturnService } from './hero-tax-return.service'; @Component({ selector: 'app-hero-tax-return', templateUrl: './hero-tax-return.component.html', styleUrls: [ './hero-tax-return.component.css' ], providers: [ HeroTaxReturnService ] }) export class HeroTaxReturnComponent { message = ''; @Output() close = new EventEmitter<void>(); get taxReturn(): HeroTaxReturn { return this.heroTaxReturnService.taxReturn; } @Input() set taxReturn(htr: HeroTaxReturn) { this.heroTaxReturnService.taxReturn = htr; } constructor(private heroTaxReturnService: HeroTaxReturnService) { } onCanceled() { this.flashMessage('Canceled'); this.heroTaxReturnService.restoreTaxReturn(); } onClose() { this.close.emit(); } onSaved() { this.flashMessage('Saved'); this.heroTaxReturnService.saveTaxReturn(); } flashMessage(msg: string) { this.message = msg; setTimeout(() => this.message = '', 500); } }
src/app/hero-tax-return.component.ts
      
      import { Component, EventEmitter, Input, Output } from '@angular/core';
import { HeroTaxReturn } from './hero';
import { HeroTaxReturnService } from './hero-tax-return.service';

@Component({
  selector: 'app-hero-tax-return',
  templateUrl: './hero-tax-return.component.html',
  styleUrls: [ './hero-tax-return.component.css' ],
  providers: [ HeroTaxReturnService ]
})
export class HeroTaxReturnComponent {
  message = '';

  @Output() close = new EventEmitter<void>();

  get taxReturn(): HeroTaxReturn {
    return this.heroTaxReturnService.taxReturn;
  }

  @Input()
  set taxReturn(htr: HeroTaxReturn) {
    this.heroTaxReturnService.taxReturn = htr;
  }

  constructor(private heroTaxReturnService: HeroTaxReturnService) { }

  onCanceled()  {
    this.flashMessage('Canceled');
    this.heroTaxReturnService.restoreTaxReturn();
  }

  onClose() { this.close.emit(); }

  onSaved() {
    this.flashMessage('Saved');
    this.heroTaxReturnService.saveTaxReturn();
  }

  flashMessage(msg: string) {
    this.message = msg;
    setTimeout(() => this.message = '', 500);
  }
}
    

透過 @Input() 屬性可以得到要編輯的報稅單,這個屬性被實現成了讀取器(getter)和設定器(setter)。 設定器根據傳進來的報稅單初始化了元件自己的 HeroTaxReturnService 實例。 讀取器總是返回該服務所存英雄的當前狀態。 元件也會請求該服務來儲存或還原這個報稅單。

The tax-return-to-edit arrives via the @Input() property, which is implemented with getters and setters. The setter initializes the component's own instance of the HeroTaxReturnService with the incoming return. The getter always returns what that service says is the current state of the hero. The component also asks the service to save and restore this tax return.

但如果該服務是一個全應用範圍的單例就不行了。 每個元件就都會共享同一個服務實例,每個元件也都會覆蓋屬於其它英雄的報稅單。

This won't work if the service is an application-wide singleton. Every component would share the same service instance, and each component would overwrite the tax return that belonged to another hero.

要防止這一點,就要在 HeroTaxReturnComponent 元資料的 providers 屬性中配置元件級的注入器,來提供該服務。

To prevent this, configure the component-level injector of HeroTaxReturnComponent to provide the service, using the providers property in the component metadata.

providers: [ HeroTaxReturnService ]
src/app/hero-tax-return.component.ts (providers)
      
      providers: [ HeroTaxReturnService ]
    

HeroTaxReturnComponent 有它自己的 HeroTaxReturnService 提供者。 回憶一下,每個元件的實例都有它自己的注入器。 在元件級提供服務可以確保元件的每個實例都得到一個自己的、私有的服務實例,而報稅單也不會再被意外覆蓋了。

The HeroTaxReturnComponent has its own provider of the HeroTaxReturnService. Recall that every component instance has its own injector. Providing the service at the component level ensures that every instance of the component gets its own, private instance of the service, and no tax return gets overwritten.

該場景程式碼中的其它部分依賴另一些 Angular 的特性和技術,你將會在本文件的其它章節學到。 你可以到現場演練 / 下載範例檢視程式碼和下載它。

The rest of the scenario code relies on other Angular features and techniques that you can learn about elsewhere in the documentation. You can review it and download it from the現場演練 / 下載範例.

場景:專門的提供者

Scenario: specialized providers

在其它層級重新提供服務的另一個理由,是在元件樹的深層中把該服務替換為一個更專門化的實現。

Another reason to re-provide a service at another level is to substitute a more specialized implementation of that service, deeper in the component tree.

考慮一個依賴於一系列服務的 Car 元件。 假設你在根注入器(代號 A)中配置了通用的提供者:CarServiceEngineServiceTiresService

Consider a Car component that depends on several services. Suppose you configured the root injector (marked as A) with generic providers for CarService, EngineService and TiresService.

你建立了一個車輛元件(A),它顯示一個從另外三個通用服務構造出的車輛。

You create a car component (A) that displays a car constructed from these three generic services.

然後,你建立一個子元件(B),它為 CarServiceEngineService 定義了自己特有的提供者,它們具有適用於元件 B 的特有能力。

Then you create a child component (B) that defines its own, specialized providers for CarService and EngineService that have special capabilities suitable for whatever is going on in component (B).

元件 B 是另一個元件 C 的父元件,而元件 C 又定義了自己的,更特殊的CarService 提供者。

Component (B) is the parent of another component (C) that defines its own, even more specialized provider for CarService.

在幕後,每個元件都有自己的注入器,這個注入器帶有為元件本身準備的 0 個、1 個或多個提供者。

Behind the scenes, each component sets up its own injector with zero, one, or more providers defined for that component itself.

當你在最深層的元件 C 解析 Car 的實例時,它使用注入器 C 解析生成了一個 Car 的實例,使用注入器 B 解析了 Engine,而 Tires 則是由根注入器 A 解析的。

When you resolve an instance of Car at the deepest component (C), its injector produces an instance of Car resolved by injector (C) with an Engine resolved by injector (B) and Tires resolved by the root injector (A).


關於依賴注入的更多知識

More on dependency injection

要了解關於 Angular 依賴注入的更多資訊,參閱 DI 提供者DI 實戰 兩章。

For more information on Angular dependency injection, see the DI Providers and DI in Action guides.