多级依赖注入器

You learned the basics of Angular Dependency injection in the Dependency Injection guide.

依赖注入一章中,我们已经学过了 Angular 依赖注入的基础知识。

Angular has a Hierarchical Dependency Injection system. There is actually a tree of injectors that parallel an application's component tree. You can reconfigure the injectors at any level of that component tree.

Angular 有一个多级依赖注入系统。 实际上,应用程序中有一个与组件树平行的注入器树(译注:平行是指结构完全相同且一一对应)。 我们可以在组件树中的任何级别上重新配置注入器,达到一些有趣和有用的效果。

This guide explores this system and how to use it to your advantage.

在本章中,我们将浏览这个体系,并告诉你如何善用它。

Try the .

试试在线例子.

The injector tree

注入器树

In the Dependency Injection guide, you learned how to configure a dependency injector and how to retrieve dependencies where you need them.

依赖注入一章中,我们学过如何配置依赖注入器,以及如何在我们需要时用它获取依赖。

In fact, there is no such thing as the injector. An application may have multiple injectors. An Angular application is a tree of components. Each component instance has its own injector! The tree of components parallels the tree of injectors.

实际上,没有那个(唯一的)注入器这回事,一个应用中可能有多个注入器。 一个 Angular 应用是一个组件树。每个组件实例都有自己的注入器! 组件的树与注入器的树平行。

Consider this guide's variation on the Tour of Heroes application . At the top is the AppComponent which has some sub- components. One of them is theHeroesListComponent. The HeroesListComponent holds and manages multiple instances of the HeroTaxReturnComponent. The following diagram represents the state of the this guide's three-level component tree when there are three instances of HeroTaxReturnComponent open simultaneously.

考虑《英雄指南》应用的一个简单变种。它的顶层是AppComponent组件,它有一些子组件。 HeroesListComponent组件保存和管理着HeroTaxReturnComponent的多个实例。 下图展示了当HeroesCardComponent的三个 HeroTaxReturnComponent 实例同时展开时的三级组件树状态。

injector tree

Angular doesn't actually create a separate injector for each component. Every component doesn't need its own injector and it would be horribly inefficient to create masses of injectors for no good purpose.

Angular实际上并没有为每个组件创建一个独立的注入器。 每个组件并不需要自己的注入器,那样做会非常低效,而且没有好处。

But every component has an injector, even if it shares that injector with another component or with the injector of the root AppModule. And there may be multiple injector instances operating at different levels of the component tree depending upon how the developer registers providers, which is the subject of this guide.

但是每个组件都一个注入器,它可能共享其它组件的注入器或使用根模块AppModule的注入器。 而且在组件树的不同层次上还可能有多个注入器实例,这取决于开发人员如何注册这些提供商。这正是本章的主题。

Injector bubbling

注入器"冒泡"

When a component requests a dependency, Angular tries to satisfy that dependency with a provider registered in that component's own injector. If the component's injector lacks the provider, it passes the request up to its parent component's injector. If that injector can't satisfy the request, it passes it along to its parent injector. The requests keep bubbling up until Angular finds an injector that can handle the request or runs out of ancestor injectors. If it runs out of ancestors, Angular throws an error.

当一个组件申请获得一个依赖时,Angular 先尝试用该组件自己的注入器来满足它。 如果该组件的注入器没有找到对应的提供商,它就把这个申请转给它父组件的注入器来处理。 如果那个注入器也无法满足这个申请,它就继续转给它的父组件的注入器。 这个申请继续往上冒泡 —— 直到我们找到了一个能处理此申请的注入器或者超出了组件树中的祖先位置为止。 如果超出了组件树中的祖先还未找到,Angular 就会抛出一个错误。

You can cap the bubbling. An intermediate component can declare that it is the "host" component. The hunt for providers will climb no higher than the injector for that host component. This a topic for another day.

我们还可以“盖住”这次冒泡。一个中层的组件可以声称自己是“宿主”组件。 向上查找提供商的过程会截止于这个“宿主”组件。 我们先保留这个问题,等改天再讨论这个选项。

Re-providing a service at different levels

You can re-register a provider for a particular dependency token at multiple levels of the injector tree. You don't have to re-register providers. You shouldn't do so unless you have a good reason. But you can.

As the resolution logic works upwards, the first provider encountered wins. Thus, a provider in an intermediate injector intercepts a request for a service from something lower in the tree. It effectively "reconfigures" and "shadows" a provider at a higher level in the tree.

If you only specify providers at the top level (typically the root AppModule), the tree of injectors appears to be flat. All requests bubble up to the root NgModule injector that you configured with the bootstrapModule method.

如果我们只在顶级(通常是根模块AppModule),这三个注入器看起来将是“平面”的。 所有的申请都会冒泡到根NgModule进行处理,也就是我们在bootstrapModule方法中配置的那个。

Component injectors

组件注入器

The ability to configure one or more providers at different levels opens up interesting and useful possibilities.

在不同层次上重新配置一个或多个提供商的能力,开启了一些既有趣又有用的可能性。

Scenario: service isolation

Architectural reasons may lead you to restrict access to a service to the application domain where it belongs.

The guide sample includes a VillainsListComponent that displays a list of villains. It gets those villains from a VillainsService.

While you could provide VillainsService in the root AppModule (that's where you'll find the HeroesService), that would make the VillainsService available everywhere in the application, including the Hero workflows.

If you later modify the VillainsService, you could break something in a hero component somewhere. That's not supposed to happen but the way you've provided the service creates that risk.

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

src/app/villains-list.component.ts (metadata)

@Component({ moduleId: module.id, selector: 'villains-list', templateUrl: './villains-list.component.html', providers: [ VillainsService ] })

By providing VillainsService in the VillainsListComponent metadata — and nowhere else —, the service becomes available only in the VillainsListComponent and its sub-component tree. It's still a singleton, but it's a singleton that exist solely in the villain domain.

You are confident that a hero component can't access it. You've reduced your exposure to error.

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 several tax returns, switching from one to the other throughout the day.

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

Heroes in action

One might suppose that the TaxReturnComponent has 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 might delegate that management to a helper service, as this example does.

Here is the HeroTaxReturnService. It 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.

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(); } }

Here is the HeroTaxReturnComponent that makes use of it.

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({ moduleId: module.id, selector: '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>(); @Input() get taxReturn(): HeroTaxReturn { return this.heroTaxReturnService.taxReturn; } 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); } }

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.

There'd be big trouble if this service were an application-wide singleton. Every component would share the same service instance. Each component would overwrite the tax return that belonged to another hero. What a mess!

Look closely at the metadata for the HeroTaxReturnComponent. Notice the providers property.

providers: [ 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. No tax return overwriting. No mess.

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 is to substitute a more specialized implementation of that service, deeper in the component tree.

Consider again the Car example from the Dependency Injection guide. Suppose you configured the root injector (marked as A) with generic providers for CarService, EngineService and TiresService.

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

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

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

car components

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

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).

car injector tree

The code for this cars scenario is in the car.components.ts and car.services.ts files of the sample which you can review and download from the