Dependency Injection

依赖注入

Dependency Injection is a powerful pattern for managing code dependencies. This cookbook explores many of the features of Dependency Injection (DI) in Angular.

依赖注入是一个用来管理代码依赖的强大模式。本文会讨论 Angular 依赖注入的许多特性。

See theof the code in this cookbook.

要获取本文的代码,参见

Application-wide dependencies

应用程序全局依赖

Register providers for dependencies used throughout the application in the @Injectable() decorator of the service itself.

在服务本身的 @Injectable() 装饰器中注册那些将被应用程序全局使用的依赖提供商。

import { Injectable } from '@angular/core'; import { HEROES } from './mock-heroes'; @Injectable({ // we declare that this service should be created // by the root application injector. providedIn: 'root', }) export class HeroService { getHeroes() { return HEROES; } }

providedIn here tells Angular that the root injector is responsible for creating an instance of the HeroService. Services that are provided this way are automatically made available to the entire application and don't need to be listed in any module.

这里的 providedIn 告诉 Angular,要由根注入器负责创建 HeroService 的实例。 所有用这种方式提供的服务,都会自动在整个应用中可用,而不必把它们显式列在任何模块中。

Service classes can act as their own providers which is why defining them in the @Injectable decorator is all the registration you need.

这些服务类可以充当自己的提供商,因此你只要把它们定义在 @Injectable 装饰器中就算注册成功了。

A provider is something that can create or deliver a service. Angular creates a service instance from a class provider by using new. Read more about providers in the Dependency Injection guide.

提供商是用来新建或者交付服务的。 Angular 拿到“类提供商”之后,会通过 new 操作来新建服务实例。 从依赖注入一章可以学到关于提供商的更多知识。

Now that you've registered these services, Angular can inject them into the constructor of any component or service, anywhere in the application.

现在你已经注册了这些服务,这样 Angular 就能在应用程序的任何地方,把它们注入到任何组件和服务的构造函数里。

External module configuration

外部模块配置

If a provider cannot be configured in the @Injectable decorator of the service, then register application-wide providers in the root AppModule, not in the AppComponent. Generally, register providers in the NgModule rather than in the root application component.

如果某个提供商不是在服务的 @Injectable 装饰器中配置的,那么就要在根模块 AppModule 中把它注册为全应用级的提供商,而不是在 AppComponent 中。 一般来说,要在 NgModule 中注册提供商,而不是在应用程序根组件中。

Do this when users should explicitly opt-in to use a service, or the service should be provided in a lazily-loaded context, or when you are configuring another application global service before the application starts.

下列情况下会用到这种方法:1. 当用户应该明确选择所用的服务时。2. 当你要在惰性加载的上下文中提供该服务时。3. 当你要在应用启动之前配置应用中的另一个全局服务时。

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.

下面的例子就属于这些情况,它为组件路由器配置了一个非默认的地址策略(location strategy),并把它加入到 AppModuleproviders 数组中。

providers: [ { provide: LocationStrategy, useClass: HashLocationStrategy } ]

@Injectable() and nested service dependencies

@Injectable和嵌套服务依赖

The consumer of an injected service does not know how to create that service. It shouldn't care. It's the dependency injection's job to create and cache that service.

这些被注入服务的消费者不需要知道如何创建这个服务,它也不应该在乎。新建和缓存这个服务是依赖注入器的工作。

Sometimes a service depends on other services, which may depend on yet other services. Resolving these nested dependencies in the correct order is also the framework's job. At each step, the consumer of dependencies simply declares what it requires in its constructor and the framework takes over.

有时候一个服务依赖其它服务...而其它服务可能依赖另外的更多服务。按正确的顺序解析这些嵌套依赖也是框架的工作。 在每一步,依赖的使用者只要在它的构造函数里简单声明它需要什么,框架就会完成所有剩下的事情。

The following example shows injecting both the LoggerService and the UserContext in the AppComponent.

下面的例子往 AppComponent 里注入的 LoggerServiceUserContext

constructor(logger: LoggerService, public userContext: UserContextService) { userContext.loadUser(this.userId); logger.logInfo('AppComponent initialized'); }

The UserContext in turn has its own dependencies on both the LoggerService and a UserService that gathers information about a particular user.

UserContext 有两个依赖 LoggerService(再一次)和负责获取特定用户信息的 UserService

@Injectable() export class UserContextService { constructor(private userService: UserService, private loggerService: LoggerService) { } }

When Angular creates the AppComponent, the dependency injection framework creates an instance of the LoggerService and starts to create the UserContextService. The UserContextService needs the LoggerService, which the framework already has, and the UserService, which it has yet to create. The UserService has no dependencies so the dependency injection framework can just use new to instantiate one.

当 Angular 新建 AppComponent 时,依赖注入框架先创建一个 LoggerService 的实例,然后创建 UserContextService 实例。 UserContextService 需要框架已经创建好的 LoggerService 实例和尚未创建的 UserService 实例。 UserService 没有其它依赖,所以依赖注入框架可以直接 new 一个实例。

The beauty of dependency injection is that AppComponent doesn't care about any of this. You simply declare what is needed in the constructor (LoggerService and UserContextService) and the framework does the rest.

依赖注入最帅的地方在于,AppComponent 的作者不需要在乎这一切。作者只是在(LoggerServiceUserContextService 的)构造函数里面简单的声明一下,框架就完成了剩下的工作。

Once all the dependencies are in place, the AppComponent displays the user information:

一旦所有依赖都准备好了,AppComponent 就会显示用户信息:

Logged In User

@Injectable()

@Injectable() 注解

Notice the @Injectable()decorator on the UserContextService class.

注意在 UserContextService 类里面的 @Injectable() 装饰器。

@Injectable() export class UserContextService { }

The @Injectable decorator indicates that the Angular DI system is used to create one or more instances of UserContextService.

@Injectable 装饰器会向 Angular DI 系统指明应该为 UserContextService 创建一个实例还是多个实例。

Limit service scope to a component subtree

把服务作用域限制到一个组件支树

All injected service dependencies are singletons meaning that, for a given dependency injector, there is only one instance of service.

所有被注入的服务依赖都是单例的,也就是说,在任意一个依赖注入器("injector")中,每个服务只有唯一的实例。

But an Angular application has multiple dependency injectors, arranged in a tree hierarchy that parallels the component tree. So a particular service can be provided and created at any component level and multiple times if provided in multiple components.

但是 Angular 应用程序有多个依赖注入器,组织成一个与组件树平行的树状结构。所以,可以在任何组件级别提供(和建立)特定的服务。如果在多个组件中注入,服务就会被新建出多个实例,分别提供给不同的组件。

By default, a service dependency provided in one component is visible to all of its child components and Angular injects the same service instance into all child components that ask for that service.

默认情况下,一个组件中注入的服务依赖,会在该组件的所有子组件中可见,而且 Angular 会把同样的服务实例注入到需要该服务的子组件中。

Accordingly, dependencies provided in the root AppComponent can be injected into any component anywhere in the application.

所以,在根部的 AppComponent 提供的依赖单例就能被注入到应用程序中任何地方任何组件。

That isn't always desirable. Sometimes you want to restrict service availability to a particular region of the application.

但这不一定总是想要的。有时候你想要把服务的有效性限制到应用程序的一个特定区域。

You can limit the scope of an injected service to a branch of the application hierarchy by providing that service at the sub-root component for that branch. This example shows how similar providing a service to a sub-root component is to providing a service in the root AppComponent. The syntax is the same. Here, the HeroService is available to the HeroesBaseComponent because it is in the providers array:

通过在组件树的子级根组件中提供服务,可以把一个被注入服务的作用域局限在应用程序结构中的某个分支中。 这个例子中展示了为子组件和根组件 AppComponent 提供服务的相似之处,它们的语法是相同的。 这里通过列入 providers 数组,在 HeroesBaseComponent 中提供了 HeroService

@Component({ selector: 'app-unsorted-heroes', template: `<div *ngFor="let hero of heroes">{{hero.name}}</div>`, providers: [HeroService] }) export class HeroesBaseComponent implements OnInit { constructor(private heroService: HeroService) { } }

When Angular creates the HeroesBaseComponent, it also creates a new instance of HeroService that is visible only to the component and its children, if any.

当 Angular 新建 HeroBaseComponent 的时候,它会同时新建一个 HeroService 实例,该实例只在该组件及其子组件(如果有)中可见。

You could also provide the HeroService to a different component elsewhere in the application. That would result in a different instance of the service, living in a different injector.

也可以在应用程序别处的不同的组件里提供 HeroService。这样就会导致在不同注入器中存在该服务的不同实例。

Examples of such scoped HeroService singletons appear throughout the accompanying sample code, including the HeroBiosComponent, HeroOfTheMonthComponent, and HeroesBaseComponent. Each of these components has its own HeroService instance managing its own independent collection of heroes.

这个例子中,局部化的 HeroService 单例,遍布整份范例代码,包括 HeroBiosComponentHeroOfTheMonthComponentHeroBaseComponent。 这些组件每个都有自己的 HeroService 实例,用来管理独立的英雄库。

Take a break!

休息一下!

This much Dependency Injection knowledge may be all that many Angular developers ever need to build their applications. It doesn't always have to be more complicated.

对一些 Angular 开发者来说,这么多依赖注入知识可能已经是它们需要知道的全部了。不是每个人都需要更复杂的用法。

Multiple service instances (sandboxing)

多个服务实例(sandboxing)

Sometimes you want multiple instances of a service at the same level of the component hierarchy.

同一个级别的组件树里,有时需要一个服务的多个实例。

A good example is a service that holds state for its companion component instance. You need a separate instance of the service for each component. Each service has its own work-state, isolated from the service-and-state of a different component. This is called sandboxing because each service and component instance has its own sandbox to play in.

一个用来保存其伴生组件的实例状态的服务就是个好例子。 每个组件都需要该服务的单独实例。 每个服务有自己的工作状态,与其它组件的服务和状态隔离。这叫做沙箱化,因为每个服务和组件实例都在自己的沙箱里运行。

Imagine a HeroBiosComponent that presents three instances of the HeroBioComponent.

想象一下,一个 HeroBiosComponent 组件显示三个 HeroBioComponent 的实例。

@Component({ selector: 'app-hero-bios', template: ` <app-hero-bio [heroId]="1"></app-hero-bio> <app-hero-bio [heroId]="2"></app-hero-bio> <app-hero-bio [heroId]="3"></app-hero-bio>`, providers: [HeroService] }) export class HeroBiosComponent { }

Each HeroBioComponent can edit a single hero's biography. A HeroBioComponent relies on a HeroCacheService to fetch, cache, and perform other persistence operations on that hero.

每个 HeroBioComponent 都能编辑一个英雄的生平。HeroBioComponent 依赖 HeroCacheService 服务来对该英雄进行读取、缓存和执行其它持久化操作。

@Injectable() export class HeroCacheService { hero: Hero; constructor(private heroService: HeroService) {} fetchCachedHero(id: number) { if (!this.hero) { this.hero = this.heroService.getHeroById(id); } return this.hero; } }

Clearly the three instances of the HeroBioComponent can't share the same HeroCacheService. They'd be competing with each other to determine which hero to cache.

很明显,这三个 HeroBioComponent 实例不能共享一样的 HeroCacheService。要不然它们会相互冲突,争相把自己的英雄放在缓存里面。

Each HeroBioComponent gets its own HeroCacheService instance by listing the HeroCacheService in its metadata providers array.

通过在自己的元数据(metadata)providers 数组里面列出 HeroCacheService, 每个 HeroBioComponent 就能拥有自己独立的 HeroCacheService 实例。

@Component({ selector: 'app-hero-bio', template: ` <h4>{{hero.name}}</h4> <ng-content></ng-content> <textarea cols="25" [(ngModel)]="hero.description"></textarea>`, providers: [HeroCacheService] }) export class HeroBioComponent implements OnInit { @Input() heroId: number; constructor(private heroCache: HeroCacheService) { } ngOnInit() { this.heroCache.fetchCachedHero(this.heroId); } get hero() { return this.heroCache.hero; } }

The parent HeroBiosComponent binds a value to the heroId. The ngOnInit passes that id to the service, which fetches and caches the hero. The getter for the hero property pulls the cached hero from the service. And the template displays this data-bound property.

父组件 HeroBiosComponent 把一个值绑定到 heroIdngOnInit 把该 id 传递到服务,然后服务获取和缓存英雄。hero 属性的 getter 从服务里面获取缓存的英雄,并在模板里显示它绑定到属性值。

Find this example inlive codeand confirm that the three HeroBioComponent instances have their own cached hero data.

在线例子中找到这个例子,确认三个 HeroBioComponent 实例拥有自己独立的英雄数据缓存。

Bios

Qualify dependency lookup with @Optional() and @Host()

使用@Optional()@Host() 装饰器来限定依赖查找方式

As you now know, dependencies can be registered at any level in the component hierarchy.

你知道,依赖可以被注入到任何组件级别。

When a component requests a dependency, Angular starts with that component's injector and walks up the injector tree until it finds the first suitable provider. Angular throws an error if it can't find the dependency during that walk.

当组件申请一个依赖时,Angular 从该组件本身的注入器开始,沿着依赖注入器的树往上找,直到找到第一个符合要求的提供商。如果 Angular 不能在这个过程中找到合适的依赖,它就会抛出一个错误。

You want this behavior most of the time. But sometimes you need to limit the search and/or accommodate a missing dependency. You can modify Angular's search behavior with the @Host and @Optional qualifying decorators, used individually or together.

大部分时候,你确实想要这个行为。 但是有时候,需要限制这个(依赖)查找逻辑,且/或提供一个缺失的依赖。 单独或联合使用 @Host@Optional 限定型装饰器,就可以修改 Angular 的查找行为。

The @Optional decorator tells Angular to continue when it can't find the dependency. Angular sets the injection parameter to null instead.

当 Angular 找不到依赖时,@Optional 装饰器会告诉 Angular 继续执行。Angular 把此注入参数设置为 null(而不用默认的抛出错误的行为)。

The @Host decorator stops the upward search at the host component.

@Host 装饰器将把往上搜索的行为截止在宿主组件

The host component is typically the component requesting the dependency. But when this component is projected into a parent component, that parent component becomes the host. The next example covers this second case.

宿主组件通常是申请这个依赖的组件。但当这个组件被投影(projected)进一个父组件后,这个父组件就变成了宿主。 下一个例子会演示第二种情况。

Demonstration

示范

The HeroBiosAndContactsComponent is a revision of the HeroBiosComponent that you looked at above.

HeroBiosAndContactsComponent前面见过的 HeroBiosComponent 的修改版。

@Component({ selector: 'app-hero-bios-and-contacts', template: ` <app-hero-bio [heroId]="1"> <app-hero-contact></app-hero-contact> </app-hero-bio> <app-hero-bio [heroId]="2"> <app-hero-contact></app-hero-contact> </app-hero-bio> <app-hero-bio [heroId]="3"> <app-hero-contact></app-hero-contact> </app-hero-bio>`, providers: [HeroService] }) export class HeroBiosAndContactsComponent { constructor(logger: LoggerService) { logger.logInfo('Creating HeroBiosAndContactsComponent'); } }

Focus on the template:

注意看模板:

template: ` <app-hero-bio [heroId]="1"> <app-hero-contact></app-hero-contact> </app-hero-bio> <app-hero-bio [heroId]="2"> <app-hero-contact></app-hero-contact> </app-hero-bio> <app-hero-bio [heroId]="3"> <app-hero-contact></app-hero-contact> </app-hero-bio>`,

Now there is a new <hero-contact> element between the <hero-bio> tags. Angular projects, or transcludes, the corresponding HeroContactComponent into the HeroBioComponent view, placing it in the <ng-content> slot of the HeroBioComponent template:

<hero-bio> 标签中是一个新的 <hero-contact> 元素。Angular 就会把相应的 HeroContactComponent投影(transclude)进 HeroBioComponent 的视图里, 将它放在 HeroBioComponent 模板的 <ng-content> 标签槽里。

template: ` <h4>{{hero.name}}</h4> <ng-content></ng-content> <textarea cols="25" [(ngModel)]="hero.description"></textarea>`,

It looks like this, with the hero's telephone number from HeroContactComponent projected above the hero description:

HeroContactComponent 获得的英雄电话号码,被投影到上面的英雄描述里,就像这样:

bio and contact

Here's the HeroContactComponent which demonstrates the qualifying decorators:

下面的 HeroContactComponent,示范了限定型装饰器(@Optional 和@Host):

@Component({ selector: 'app-hero-contact', template: ` <div>Phone #: {{phoneNumber}} <span *ngIf="hasLogger">!!!</span></div>` }) export class HeroContactComponent { hasLogger = false; constructor( @Host() // limit to the host component's instance of the HeroCacheService private heroCache: HeroCacheService, @Host() // limit search for logger; hides the application-wide logger @Optional() // ok if the logger doesn't exist private loggerService: LoggerService ) { if (loggerService) { this.hasLogger = true; loggerService.logInfo('HeroContactComponent can log!'); } } get phoneNumber() { return this.heroCache.hero.phone; } }

Focus on the constructor parameters:

注意看构造函数的参数:

@Host() // limit to the host component's instance of the HeroCacheService private heroCache: HeroCacheService, @Host() // limit search for logger; hides the application-wide logger @Optional() // ok if the logger doesn't exist private loggerService: LoggerService

The @Host() function decorating the heroCache property ensures that you get a reference to the cache service from the parent HeroBioComponent. Angular throws an error if the parent lacks that service, even if a component higher in the component tree happens to have it.

@Host() 函数是 heroCache 属性的装饰器,确保从其父组件 HeroBioComponent 得到一个缓存服务。如果该父组件不存在这个服务,Angular 就会抛出错误,即使组件树里的再上级有某个组件拥有这个服务,Angular 也会抛出错误。

A second @Host() function decorates the loggerService property. The only LoggerService instance in the app is provided at the AppComponent level. The host HeroBioComponent doesn't have its own LoggerService provider.

另一个 @Host() 函数是属性 loggerService 的装饰器。 在本应用程序中只有一个在 AppComponent 级提供的 LoggerService 实例。 该宿主 HeroBioComponent 没有自己的 LoggerService 提供商。

Angular would throw an error if you hadn't also decorated the property with the @Optional() function. Thanks to @Optional(), Angular sets the loggerService to null and the rest of the component adapts.

如果没有同时使用 @Optional() 装饰器的话,Angular 就会抛出错误。多亏了 @Optional(),Angular 把 loggerService 设置为 null,并继续执行组件而不会抛出错误。

Here's the HeroBiosAndContactsComponent in action.

下面是 HeroBiosAndContactsComponent 的执行结果:

Bios with contact into

If you comment out the @Host() decorator, Angular now walks up the injector ancestor tree until it finds the logger at the AppComponent level. The logger logic kicks in and the hero display updates with the gratuitous "!!!", indicating that the logger was found.

如果注释掉 @Host() 装饰器,Angular 就会沿着注入器树往上走,直到在 AppComponent 中找到该日志服务。日志服务的逻辑加入进来,更新了英雄的显示信息,这表明确实找到了日志服务。

Without @Host

On the other hand, if you restore the @Host() decorator and comment out @Optional, the application fails for lack of the required logger at the host component level.
EXCEPTION: No provider for LoggerService! (HeroContactComponent -> LoggerService)

另一方面,如果恢复 @Host() 装饰器,注释掉 @Optional,应用程序就会运行失败,因为它在宿主组件级别找不到需要的日志服务。
EXCEPTION: No provider for LoggerService! (HeroContactComponent -> LoggerService)

Inject the component's DOM element

注入组件的 DOM 元素

On occasion you might need to access a component's corresponding DOM element. Although developers strive to avoid it, many visual effects and 3rd party tools, such as jQuery, require DOM access.

偶尔,可能需要访问一个组件对应的 DOM 元素。尽量避免这样做,但还是有很多视觉效果和第三方工具(比如 jQuery)需要访问 DOM。

To illustrate, here's a simplified version of the HighlightDirective from the Attribute Directives page.

要说明这一点,请在属性型指令HighlightDirective 的基础上,编写一个简化版。

import { Directive, ElementRef, HostListener, Input } from '@angular/core'; @Directive({ selector: '[appHighlight]' }) export class HighlightDirective { @Input('appHighlight') highlightColor: string; private el: HTMLElement; constructor(el: ElementRef) { this.el = el.nativeElement; } @HostListener('mouseenter') onMouseEnter() { this.highlight(this.highlightColor || 'cyan'); } @HostListener('mouseleave') onMouseLeave() { this.highlight(null); } private highlight(color: string) { this.el.style.backgroundColor = color; } }

The directive sets the background to a highlight color when the user mouses over the DOM element to which it is applied.

当用户把鼠标移到 DOM 元素上时,指令将该元素的背景设置为一个高亮颜色。

Angular sets the constructor's el parameter to the injected ElementRef, which is a wrapper around that DOM element. Its nativeElement property exposes the DOM element for the directive to manipulate.

Angular 把构造函数参数 el 设置为注入的 ElementRef,该 ElementRef 代表了宿主的 DOM 元素, 它的 nativeElement 属性把该 DOM 元素暴露给了指令。

The sample code applies the directive's myHighlight attribute to two <div> tags, first without a value (yielding the default color) and then with an assigned color value.

下面的代码把指令的 myHighlight 属性(Attribute)填加到两个 <div> 标签里,一个没有赋值,一个赋值了颜色。

<div id="highlight" class="di-component" appHighlight> <h3>Hero Bios and Contacts</h3> <div appHighlight="yellow"> <app-hero-bios-and-contacts></app-hero-bios-and-contacts> </div> </div>

The following image shows the effect of mousing over the <hero-bios-and-contacts> tag.

下图显示了鼠标移到 <hero-bios-and-contacts> 标签的效果:

Highlighted bios

Define dependencies with providers

使用提供商来定义依赖

This section demonstrates how to write providers that deliver dependent services.

本节将演示如何编写提供商来提供被依赖的服务。

Get a service from a dependency injector by giving it a token.

给依赖注入器提供令牌来获取服务。

You usually let Angular handle this transaction by specifying a constructor parameter and its type. The parameter type serves as the injector lookup token. Angular passes this token to the injector and assigns the result to the parameter. Here's a typical example:

你通常在构造函数里面,为参数指定类型,让 Angular 来处理依赖注入。该参数类型就是依赖注入器所需的令牌。 Angular 把该令牌传给注入器,然后把得到的结果赋给参数。下面是一个典型的例子:

constructor(logger: LoggerService) { logger.logInfo('Creating HeroBiosComponent'); }

Angular asks the injector for the service associated with the LoggerService and assigns the returned value to the logger parameter.

Angular 向注入器请求与 LoggerService 对应的服务,并将返回值赋给 logger 参数。

Where did the injector get that value? It may already have that value in its internal container. If it doesn't, it may be able to make one with the help of a provider. A provider is a recipe for delivering a service associated with a token.

注入器从哪得到的依赖? 它可能在自己内部容器里已经有该依赖了。 如果它没有,也能在提供商的帮助下新建一个。 提供商就是一个用于交付服务的配方,它被关联到一个令牌。

If the injector doesn't have a provider for the requested token, it delegates the request to its parent injector, where the process repeats until there are no more injectors. If the search is futile, the injector throws an error—unless the request was optional.

如果注入器无法根据令牌在自己内部找到对应的提供商,它便将请求移交给它的父级注入器,这个过程不断重复,直到没有更多注入器为止。 如果没找到,注入器就抛出一个错误...除非这个请求是可选的

A new injector has no providers. Angular initializes the injectors it creates with some providers it cares about. You have to register your own application providers manually, usually in the @Injectable decorator of the service, providers array of the NgModule or Directive metadata:

新建的注入器中没有提供商。 Angular 会使用一些自带的提供商来初始化这些注入器。你必须自行注册属于自己的提供商,通常会在该服务的 @Injectable 装饰器中,或在 NgModuleDirective 元数据的 providers 数组中进行注册。

providers: [ LoggerService, UserContextService, UserService ]

Defining providers

定义提供商

The simple way of defining providers in the @Injectable decorator of the class is recommended.

建议直接在服务类的 @Injectable 装饰器中定义服务提供商。

import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class HeroService { constructor() { } }

Another alternative is to mention the class in the providers array of the @NgModule and you're done.

备选方案是在 @NgModuleproviders 数组中引用下这个类就可以了。

providers: [HeroService]

It's that simple because the most common injected service is an instance of a class. But not every dependency can be satisfied by creating a new instance of a class. You need other ways to deliver dependency values and that means you need other ways to specify a provider.

注册类提供商之所以这么简单,是因为最常见的可注入服务就是一个类的实例。 但是,并不是所有的依赖都只要创建一个类的新实例就可以交付了。你还需要其它的交付方式,这意味着你也要用其它方式来指定提供商。

The HeroOfTheMonthComponent example demonstrates many of the alternatives and why you need them. It's visually simple: a few properties and the logs produced by a logger.

HeroOfTheMonthComponent 例子示范了一些替代方案,展示了为什么需要它们。 它看起来很简单:一些属性和一个日志输出。

Hero of the month

The code behind it gives you plenty to think about.

这段代码的背后有很多值得深入思考的地方。

import { Component, Inject } from '@angular/core'; import { DateLoggerService } from './date-logger.service'; import { Hero } from './hero'; import { HeroService } from './hero.service'; import { LoggerService } from './logger.service'; import { MinimalLogger } from './minimal-logger.service'; import { RUNNERS_UP, runnersUpFactory } from './runners-up'; @Component({ selector: 'app-hero-of-the-month', templateUrl: './hero-of-the-month.component.html', providers: [ { provide: Hero, useValue: someHero }, { provide: TITLE, useValue: 'Hero of the Month' }, { provide: HeroService, useClass: HeroService }, { provide: LoggerService, useClass: DateLoggerService }, { provide: MinimalLogger, useExisting: LoggerService }, { provide: RUNNERS_UP, useFactory: runnersUpFactory(2), deps: [Hero, HeroService] } ] }) export class HeroOfTheMonthComponent { logs: string[] = []; constructor( logger: MinimalLogger, public heroOfTheMonth: Hero, @Inject(RUNNERS_UP) public runnersUp: string, @Inject(TITLE) public title: string) { this.logs = logger.logs; logger.logInfo('starting up'); } }

The provide object literal

provide 对象

The provide object literal takes a token and a definition object. The token is usually a class but it doesn't have to be.

provide 对象需要一个令牌和一个定义对象。该令牌通常是一个类,但并非一定是

The definition object has a required property that specifies how to create the singleton instance of the service. In this case, the property.

定义对象有一个必填属性(即 useValue),用来标识该提供商会如何新建和返回该服务的单例对象。

useValue—the value provider

useValue - *值-提供商

Set the useValue property to a fixed value that the provider can return as the service instance (AKA, the "dependency object").

把一个*固定的值,也就是该提供商可以将其作为依赖对象返回的值,赋给 useValue 属性。

Use this technique to provide runtime configuration constants such as website base addresses and feature flags. You can use a value provider in a unit test to replace a production service with a fake or mock.

使用该技巧来进行运行期常量设置,比如网站的基础地址和功能标志等。 你通常在单元测试中使用值-提供商,用一个假的或模仿的(服务)来取代一个生产环境的服务。

The HeroOfTheMonthComponent example has two value providers. The first provides an instance of the Hero class; the second specifies a literal string resource:

HeroOfTheMonthComponent 例子有两个值-提供商。 第一个提供了一个 Hero 类的实例;第二个指定了一个字符串资源:

{ provide: Hero, useValue: someHero }, { provide: TITLE, useValue: 'Hero of the Month' },

The Hero provider token is a class which makes sense because the value is a Hero and the consumer of the injected hero would want the type information.

Hero 提供商的令牌是一个类,这很合理,因为它提供的结果是一个 Hero 实例,并且被注入该英雄的消费者也需要知道它类型信息。

The TITLE provider token is not a class. It's a special kind of provider lookup key called an InjectionToken. You can use an InjectionToken for any kind of provider but it's particular helpful when the dependency is a simple value like a string, a number, or a function.

TITLE 提供商的令牌不是一个类。它是一个特别类型的提供商查询键,名叫InjectionToken. 你可以把 InjectionToken 用作任何类型的提供商的令牌,但是它在依赖是简单类型(比如字符串、数字、函数)时会特别有帮助。

The value of a value provider must be defined now. You can't create the value later. Obviously the title string literal is immediately available. The someHero variable in this example was set earlier in the file:

一个值-提供商的值必须要立即定义。不能事后再定义它的值。很显然,标题字符串是立刻可用的。 该例中的 someHero 变量是以前在下面这个文件中定义的:

const someHero = new Hero(42, 'Magma', 'Had a great month!', '555-555-5555');

The other providers create their values lazily when they're needed for injection.

其它提供商只在需要注入它们的时候才创建并惰性加载它们的值。

useClass—the class provider

useClass - 类-提供商

The useClass provider creates and returns new instance of the specified class.

userClass 提供商创建并返回一个指定类的新实例。

Use this technique to substitute an alternative implementation for a common or default class. The alternative could implement a different strategy, extend the default class, or fake the behavior of the real class in a test case.

使用该技术来为公共或默认类提供备选实现。该替代品能实现一个不同的策略,比如拓展默认类或者在测试的时候假冒真实类。

Here are two examples in the HeroOfTheMonthComponent:

请看下面 HeroOfTheMonthComponent 里的两个例子:

{ provide: HeroService, useClass: HeroService }, { provide: LoggerService, useClass: DateLoggerService },

The first provider is the de-sugared, expanded form of the most typical case in which the class to be created (HeroService) is also the provider's dependency injection token. It's in this long form to de-mystify the preferred short form.

第一个提供商是展开了语法糖的,是一个典型情况的展开。一般来说,被新建的类(HeroService)同时也是该提供商的注入令牌。 这里用完整形态来编写它,来反衬更受欢迎的缩写形式。

The second provider substitutes the DateLoggerService for the LoggerService. The LoggerService is already registered at the AppComponent level. When this component requests the LoggerService, it receives the DateLoggerService instead.

第二个提供商使用 DateLoggerService 来满足 LoggerService。该 LoggerServiceAppComponent 级别已经被注册。当这个组件要求 LoggerService 的时候,它得到的却是 DateLoggerService 服务。

This component and its tree of child components receive the DateLoggerService instance. Components outside the tree continue to receive the original LoggerService instance.

这个组件及其子组件会得到 DateLoggerService 实例。这个组件树之外的组件得到的仍是 LoggerService 实例。

The DateLoggerService inherits from LoggerService; it appends the current date/time to each message:

DateLoggerServiceLoggerService 继承;它把当前的日期/时间附加到每条信息上。

@Injectable() export class DateLoggerService extends LoggerService { logInfo(msg: any) { super.logInfo(stamp(msg)); } logDebug(msg: any) { super.logInfo(stamp(msg)); } logError(msg: any) { super.logError(stamp(msg)); } } function stamp(msg: any) { return msg + ' at ' + new Date(); }

useExisting—the alias provider

useExisting - 别名-提供商

The useExisting provider maps one token to another. In effect, the first token is an alias for the service associated with the second token, creating two ways to access the same service object.

使用 useExisting,提供商可以把一个令牌映射到另一个令牌上。实际上,第一个令牌是第二个令牌所对应的服务的一个别名,创造了访问同一个服务对象的两种方法

{ provide: MinimalLogger, useExisting: LoggerService },

Narrowing an API through an aliasing interface is one important use case for this technique. The following example shows aliasing for that purpose.

通过使用别名接口来把一个 API 变窄,是一个很重要的该技巧的使用例子。下面的例子中使用别名就是为了这个目的。

Imagine that the LoggerService had a large API, much larger than the actual three methods and a property. You might want to shrink that API surface to just the members you actually need. Here the MinimalLogger class-interface reduces the API to two members:

想象一下如果 LoggerService 有个很大的 API 接口(虽然它其实只有三个方法,一个属性),通过使用 MinimalLogger类-接口别名,就能成功的把这个 API 接口缩小到只暴露两个成员:

// Class used as a "narrowing" interface that exposes a minimal logger // Other members of the actual implementation are invisible export abstract class MinimalLogger { logs: string[]; logInfo: (msg: string) => void; }

Now put it to use in a simplified version of the HeroOfTheMonthComponent.

现在,在一个简化版的 HeroOfTheMonthComponent 中使用它。

@Component({ selector: 'app-hero-of-the-month', templateUrl: './hero-of-the-month.component.html', // TODO: move this aliasing, `useExisting` provider to the AppModule providers: [{ provide: MinimalLogger, useExisting: LoggerService }] }) export class HeroOfTheMonthComponent { logs: string[] = []; constructor(logger: MinimalLogger) { logger.logInfo('starting up'); } }

The HeroOfTheMonthComponent constructor's logger parameter is typed as MinimalLogger so only the logs and logInfo members are visible in a TypeScript-aware editor:

HeroOfTheMonthComponent 构造函数的 logger 参数是一个 MinimalLogger 类型,支持 TypeScript 的编辑器里,只能看到它的两个成员 logslogInfo

MinimalLogger restricted API

Behind the scenes, Angular actually sets the logger parameter to the full service registered under the LoggingService token which happens to be the DateLoggerService that was provided above.

实际上,Angular 确实想把 logger 参数设置为注入器里 LoggerService 的完整版本。只是在之前的提供商注册里使用了 useClass, 所以该完整版本被 DateLoggerService 取代了。

The following image, which displays the logging date, confirms the point:

在下面的图片中,显示了日志日期,可以确认这一点:

DateLoggerService entry

useFactory—the factory provider

useFactory - 工厂-提供商

The useFactory provider creates a dependency object by calling a factory function as in this example.

useFactory 提供商通过调用工厂函数来新建一个依赖对象,如下例所示。

{ provide: RUNNERS_UP, useFactory: runnersUpFactory(2), deps: [Hero, HeroService] }

Use this technique to create a dependency object with a factory function whose inputs are some combination of injected services and local state.

使用这项技术,可以用包含了一些依赖服务和本地状态输入的工厂函数来建立一个依赖对象

The dependency object doesn't have to be a class instance. It could be anything. In this example, the dependency object is a string of the names of the runners-up to the "Hero of the Month" contest.

依赖对象不一定是一个类实例。它可以是任何东西。在这个例子里,依赖对象是一个字符串,代表了本月英雄比赛的亚军的名字。

The local state is the number 2, the number of runners-up this component should show. It executes runnersUpFactory immediately with 2.

本地状态是数字 2,该组件应该显示的亚军的个数。它就会立刻用 2 来执行 runnersUpFactory

The runnersUpFactory itself isn't the provider factory function. The true provider factory function is the function that runnersUpFactory returns.

runnersUpFactory 自身不是提供商工厂函数。真正的提供商工厂函数是 runnersUpFactory 返回的函数。

export function runnersUpFactory(take: number) { return (winner: Hero, heroService: HeroService): string => { /* ... */ }; };

That returned function takes a winning Hero and a HeroService as arguments.

这个返回的函数需要一个 Hero 和一个 HeroService 参数。

Angular supplies these arguments from injected values identified by the two tokens in the deps array. The two deps values are tokens that the injector uses to provide these factory function dependencies.

Angular 通过使用 deps 数组中的两个令牌,来识别注入的值,用来提供这些参数。这两个 deps 值是供注入器使用的令牌,用来提供工厂函数的依赖。

After some undisclosed work, the function returns the string of names and Angular injects it into the runnersUp parameter of the HeroOfTheMonthComponent.

一些内部工作后,这个函数返回名字字符串,Angular 将其注入到 HeroOfTheMonthComponent 组件的 runnersUp 参数里。

The function retrieves candidate heroes from the HeroService, takes 2 of them to be the runners-up, and returns their concatenated names. Look at thefor the full source code.

该函数从 HeroService 获取英雄参赛者,从中取 2 个作为亚军,并把他们的名字拼接起来。请到查看全部原代码。

Provider token alternatives: the class-interface and InjectionToken

备选提供商令牌:类-接口InjectionToken

Angular dependency injection is easiest when the provider token is a class that is also the type of the returned dependency object, or what you usually call the service.

Angular 依赖注入当令牌是类的时候是最简单的,该类同时也是返回的依赖对象的类型(通常直接称之为服务)。

But the token doesn't have to be a class and even when it is a class, it doesn't have to be the same type as the returned object. That's the subject of the next section.

但令牌不一定都是类,就算它是一个类,它也不一定都返回类型相同的对象。这是下一节的主题。

class-interface

类-接口

The previous Hero of the Month example used the MinimalLogger class as the token for a provider of a LoggerService.

前面的月度英雄的例子使用了 MinimalLogger 类作为 LoggerService 提供商的令牌。

{ provide: MinimalLogger, useExisting: LoggerService },

The MinimalLogger is an abstract class.

MinimalLogger 是一个抽象类。

// Class used as a "narrowing" interface that exposes a minimal logger // Other members of the actual implementation are invisible export abstract class MinimalLogger { logs: string[]; logInfo: (msg: string) => void; }

You usually inherit from an abstract class. But no class in this application inherits from MinimalLogger.

你通常从一个抽象类继承。但这个应用中并没有类会继承 MinimalLogger

The LoggerService and the DateLoggerService could have inherited from MinimalLogger. They could have implemented it instead in the manner of an interface. But they did neither. The MinimalLogger is used exclusively as a dependency injection token.

LoggerServiceDateLoggerService本可以MinimalLogger 中继承。 它们也可以实现 MinimalLogger,而不用单独定义接口。 但它们没有。 MinimalLogger 在这里仅仅被用作一个 "依赖注入令牌"。

When you use a class this way, it's called a class-interface. The key benefit of a class-interface is that you can get the strong-typing of an interface and you can use it as a provider token in the way you would a normal class.

这种用法的类叫做类-接口。它关键的好处是:提供了接口的强类型,能像正常类一样把它当做提供商令牌使用

A class-interface should define only the members that its consumers are allowed to call. Such a narrowing interface helps decouple the concrete class from its consumers.

类-接口应该定义允许它的消费者调用的成员。窄的接口有助于解耦该类的具体实现和它的消费者。

Why MinimalLogger is a class and not a TypeScript interface

为什么 MinimalLogger 是一个类而不是一个 TypeScript 接口

You can't use an interface as a provider token because interfaces are not JavaScript objects. They exist only in the TypeScript design space. They disappear after the code is transpiled to JavaScript.

不能把接口当做提供商的令牌,因为接口不是有效的 JavaScript 对象。 它们只存在在 TypeScript 的设计空间里。它们会在被编译为 JavaScript 之后消失。

A provider token must be a real JavaScript object of some kind: such as a function, an object, a string, or a class.

一个提供商令牌必须是一个真实的 JavaScript 对象,比如:一个函数,一个对象,一个字符串,或一个类。

Using a class as an interface gives you the characteristics of an interface in a real JavaScript object.

把类当做接口使用,可以为你在一个 JavaScript 对象上提供类似于接口的特性。

Of course a real object occupies memory. To minimize memory cost, the class should have no implementation. The MinimalLogger transpiles to this unoptimized, pre-minified JavaScript for a constructor function:

当然,一个真实的类会占用内存。为了节省内存占用,该类应该没有具体的实现MinimalLogger 会被转译成下面这段没有优化过的,尚未最小化的 JavaScript:

var MinimalLogger = (function () { function MinimalLogger() {} return MinimalLogger; }()); exports("MinimalLogger", MinimalLogger);

Notice that it doesn't have a single member. It never grows no matter how many members you add to the class as long as those members are typed but not implemented. Look again at the TypeScript MinimalLogger class to confirm that it has no implementation.

注意,只要不实现它,不管添加多少成员,它永远不会增长大小。

InjectionToken

InjectionToken

Dependency objects can be simple values like dates, numbers and strings, or shapeless objects like arrays and functions.

依赖对象可以是一个简单的值,比如日期,数字和字符串,或者一个无形的对象,比如数组和函数。

Such objects don't have application interfaces and therefore aren't well represented by a class. They're better represented by a token that is both unique and symbolic, a JavaScript object that has a friendly name but won't conflict with another token that happens to have the same name.

这样的对象没有应用程序接口,所以不能用一个类来表示。更适合表示它们的是:唯一的和符号性的令牌,一个 JavaScript 对象,拥有一个友好的名字,但不会与其它的同名令牌发生冲突。

The InjectionToken has these characteristics. You encountered them twice in the Hero of the Month example, in the title value provider and in the runnersUp factory provider.

InjectionToken 具有这些特征。在Hero of the Month例子中遇见它们两次,一个是 title 的值,一个是 runnersUp 工厂提供商。

{ provide: TITLE, useValue: 'Hero of the Month' }, { provide: RUNNERS_UP, useFactory: runnersUpFactory(2), deps: [Hero, HeroService] }

You created the TITLE token like this:

这样创建 TITLE 令牌:

import { InjectionToken } from '@angular/core'; export const TITLE = new InjectionToken<string>('title');

The type parameter, while optional, conveys the dependency's type to developers and tooling. The token description is another developer aid.

类型参数,虽然是可选的,但可以向开发者和开发工具传达类型信息。 而且这个令牌的描述信息也可以为开发者提供帮助。

Inject into a derived class

注入到派生类

Take care when writing a component that inherits from another component. If the base component has injected dependencies, you must re-provide and re-inject them in the derived class and then pass them down to the base class through the constructor.

当编写一个继承自另一个组件的组件时,要格外小心。如果基础组件有依赖注入,必须要在派生类中重新提供和重新注入它们,并将它们通过构造函数传给基类。

In this contrived example, SortedHeroesComponent inherits from HeroesBaseComponent to display a sorted list of heroes.

在这个刻意生成的例子里,SortedHeroesComponent 继承自 HeroesBaseComponent,显示一个被排序的英雄列表。

Sorted Heroes

The HeroesBaseComponent could stand on its own. It demands its own instance of the HeroService to get heroes and displays them in the order they arrive from the database.

HeroesBaseComponent 能自己独立运行。它在自己的实例里要求 HeroService,用来得到英雄,并将他们按照数据库返回的顺序显示出来。

@Component({ selector: 'app-unsorted-heroes', template: `<div *ngFor="let hero of heroes">{{hero.name}}</div>`, providers: [HeroService] }) export class HeroesBaseComponent implements OnInit { constructor(private heroService: HeroService) { } heroes: Array<Hero>; ngOnInit() { this.heroes = this.heroService.getAllHeroes(); this.afterGetHeroes(); } // Post-process heroes in derived class override. protected afterGetHeroes() {} }

Keep constructors simple. They should do little more than initialize variables. This rule makes the component safe to construct under test without fear that it will do something dramatic like talk to the server. That's why you call the HeroService from within the ngOnInit rather than the constructor.

让构造函数保持简单。它们只应该用来初始化变量。 这条规则用于在测试环境中放心的构造组件,以免在构造它们时,无意做了一些非常戏剧化的动作(比如与服务器进行会话)。 这就是为什么你要在 ngOnInit 里面调用 HeroService,而不是在构造函数中。

Users want to see the heroes in alphabetical order. Rather than modify the original component, sub-class it and create a SortedHeroesComponent that sorts the heroes before presenting them. The SortedHeroesComponent lets the base class fetch the heroes.

用户希望看到英雄按字母顺序排序。与其修改原始的组件,不如派生它,新建 SortedHeroesComponent,以便展示英雄之前进行排序。 SortedHeroesComponent 让基类来获取英雄。

Unfortunately, Angular cannot inject the HeroService directly into the base class. You must provide the HeroService again for this component, then pass it down to the base class inside the constructor.

可惜,Angular 不能直接在基类里直接注入 HeroService。必须在这个组件里再次提供 HeroService,然后通过构造函数传给基类。

@Component({ selector: 'app-sorted-heroes', template: `<div *ngFor="let hero of heroes">{{hero.name}}</div>`, providers: [HeroService] }) export class SortedHeroesComponent extends HeroesBaseComponent { constructor(heroService: HeroService) { super(heroService); } protected afterGetHeroes() { this.heroes = this.heroes.sort((h1, h2) => { return h1.name < h2.name ? -1 : (h1.name > h2.name ? 1 : 0); }); } }

Now take note of the afterGetHeroes() method. Your first instinct might have been to create an ngOnInit method in SortedHeroesComponent and do the sorting there. But Angular calls the derived class's ngOnInit before calling the base class's ngOnInit so you'd be sorting the heroes array before they arrived. That produces a nasty error.

现在,请注意 afterGetHeroes() 方法。 你的第一反应是在 SortedHeroesComponent 组件里面建一个 ngOnInit 方法来做排序。但是 Angular 会先调用派生类的 ngOnInit,后调用基类的 ngOnInit, 所以可能在英雄到达之前就开始排序。这就产生了一个讨厌的错误。

Overriding the base class's afterGetHeroes() method solves the problem.

覆盖基类的 afterGetHeroes() 方法可以解决这个问题。

These complications argue for avoiding component inheritance.

分析上面的这些复杂性是为了强调避免使用组件继承这一点。

Find a parent component by injection

通过注入来找到一个父组件

Application components often need to share information. More loosely coupled techniques such as data binding and service sharing are preferable. But sometimes it makes sense for one component to have a direct reference to another component perhaps to access values or call methods on that component.

应用程序组件经常需要共享信息。使用松耦合的技术会更好一点,比如数据绑定和服务共享。 但有时候组件确实需要拥有另一个组件的引用,用来访问该组件的属性值或者调用它的方法。

Obtaining a component reference is a bit tricky in Angular. Although an Angular application is a tree of components, there is no public API for inspecting and traversing that tree.

在 Angular 里,获取一个组件的引用比较复杂。虽然 Angular 应用程序是一个组件树,但它没有公共 API 来在该树中巡查和穿梭。

There is an API for acquiring a child reference. Check out Query, QueryList, ViewChildren, and ContentChildren in the API Reference.

有一个 API 可以获取子级的引用(请看API 参考手册中的 Query, QueryList, ViewChildren,和 ContentChildren)。

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

但没有公共 API 来获取父组件的引用。但是因为每个组件的实例都被加到了依赖注入器的容器中,可以使用 Angular 依赖注入来找到父组件。

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.

你使用标准的类注入来获取已知类型的父组件。

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

在下面的例子中,父组件 AlexComponent 有几个子组件,包括 CathyComponent:

@Component({ selector: 'alex', template: ` <div class="a"> <h3>{{name}}</h3> <cathy></cathy> <craig></craig> <carol></carol> </div>`, }) export class AlexComponent extends Base { name = 'Alex'; }

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

在注入AlexComponent` 进来后,Cathy 报告它是否对 Alex* 有访问权:

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

Notice that even though the @Optional qualifier is there for safety, theconfirms that the alex parameter is set.

注意,这里为安全起见而添加了@Optional装饰器,显示 alex 参数确实被设置了。

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

一个可复用的组件可能是多个组件的子级。想象一个用来渲染金融工具头条新闻的组件。由于商业原因,该新闻组件在实时变化的市场数据流过时,要频繁的直接调用其父级工具。

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.

该应用程序可能有多于一打的金融工具组件。如果幸运,它们可能会从同一个基类派生,其 API 是 NewsComponent 组件所能理解的。

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.

更好的方式是通过接口来寻找实现了它的组件。但这是不可能的,因为 TypeScript 的接口在编译成 JavaScript 以后就消失了,JavaScript 不支持接口。没有东西可查。

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

这并不是好的设计。问题是一个组件是否能通过它父组件的基类来注入它的父组件呢

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

CraigComponent 例子探究了这个问题。[往回看 Alex]{guide/dependency-injection-in-action#alex},你看到 Alex 组件扩展(派生)自一个叫 Base 的类。

export class AlexComponent extends Base

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

CraigComponent 试图把 Base 注入到到它的 alex 构造函数参数,来报告是否成功。

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

Unfortunately, this does not work. Theconfirms that the alex parameter is null. You cannot inject a parent by its base class.

可惜这样不行。显示 alex 参数是 null。 不能通过基类注入父组件

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.

该父组件必须通过提供一个与类-接口令牌同名的别名来与之合作。

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

请记住 Angular 总是从它自己的注入器添加一个组件实例;这就是为什么在之前可以 Alex 注入到 Carol

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:

编写一个别名提供商 &mdash;一个拥有 useExisting 定义的 provide 函数 — 它新建一个备选的方式来注入同一个组件实例,并把这个提供商添加到 AlexComponent@Component 元数据里的 providers 数组。

providers: [{ provide: Parent, useExisting: 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.

Parent是该提供商的类-接口令牌。AlexComponent 引用了自身,造成循环引用,使用forwardRef打破了该循环。

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

CarolAlex 的第三个子组件,把父级注入到了自己的 parent 参数,和之前做的一样:

export class CarolComponent { name = 'Carol'; constructor( @Optional() public parent: Parent ) { } }

Here's Alex and family in action:

下面是 Alex 和其家庭的运行结果:

Alex in action

Find the parent in a tree of parents with @SkipSelf()

通过父级树找到父组件

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

想象组件树中的一个分支为:Alice -> Barry -> CarolAliceBarry 都实现了这个 Parent类-接口

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 是个问题。它需要访问它的父组件 Alice,但同时它也是 Carol 的父组件。这个意味着它必须同时注入Parent类-接口来获取 Alice,和提供一个 Parent 来满足 Carol

Here's Barry:

下面是 Barry 的代码:

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

Barryproviders 数组看起来很像Alex 的那个. 如果准备一直像这样编写别名提供商的话,你应该建立一个辅助函数

For now, focus on Barry's constructor:

眼下,请注意 Barry 的构造函数:

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

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

除额外添加了一个的 @SkipSelf 外,它和 Carol 的构造函数一样。

@SkipSelf is essential for two reasons:

添加 @SkipSelf 主要是出于两个原因:

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

    它告诉注入器从一个在自己上一级的组件开始搜索一个 Parent 依赖。

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

    如果没写 @SkipSelf 装饰器的话,Angular 就会抛出一个循环依赖错误。

    Cannot instantiate cyclic dependency! (BethComponent -> Parent -> BethComponent)

    不能创建循环依赖实例!(BethComponent -> Parent -> BethComponent)

Here's Alice, Barry and family in action:

这里是 AliceBarry 和该家庭的操作演示:

Alice in action

The Parent class-interface

Parent 类-接口

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

以前学过类-接口是一个抽象类,被当成一个接口使用,而非基类。

The example defines a Parent class-interface.

这个例子定义了一个 Parent类-接口

export abstract class Parent { name: string; }

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.

Parent类-接口定义了 Name 属性,它有类型声明,但是没有实现,该 name 是该父级的所有子组件们唯一能调用的属性。 这种“窄接口”有助于解耦子组件类和它的父组件。

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

一个能用做父级的组件应该实现类-接口,和下面的 AliceComponent 的做法一样:

export class AliceComponent implements Parent

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

这样做可以提升代码的清晰度,但严格来说并不是必须的。虽然 AlexComponent 有一个 name 属性(来自 Base 类的要求),但它的类签名并不需要提及 Parent

export class AlexComponent extends Base

The 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

为了正确的代码风格,该 AlexComponent应该实现 Parent。在这个例子里它没有这样,只是为了演示在没有该接口的情况下,该代码仍会被正确编译并运行。

A provideParent() helper function

provideParent()助手函数

Writing variations of the same parent alias provider gets old quickly, especially this awful mouthful with a forwardRef:

编写父组件相同的各种别名提供商很快就会变得啰嗦,在用forwardRef的时候尤其绕口:

providers: [{ provide: Parent, useExisting: forwardRef(() => AlexComponent) }],

You can extract that logic into a helper function like this:

可以像这样把该逻辑抽取到一个助手函数里:

// Helper method to provide the current component instance in the name of a `parentType`. const provideParent = (component: any) => { return { provide: Parent, useExisting: forwardRef(() => component) }; };

Now you can add a simpler, more meaningful parent provider to your components:

现在就可以为组件添加一个更简单、直观的父级提供商了:

providers: [ provideParent(AliceComponent) ]

You can do better. The current version of the helper function can only alias the Parent class-interface. The application might have a variety of parent types, each with its own class-interface token.

你可以做得更好。当前版本的助手函数只能为 Parent类-接口提供别名。应用程序可能有很多类型的父组件,每个父组件有自己的类-接口令牌。

Here's a revised version that defaults to parent but also accepts an optional second parameter for a different parent class-interface.

下面是一个修改版本,默认接受一个 Parent,但同时接受一个可选的第二参数,可以用来指定一个不同的父级类-接口

// Helper method to provide the current component instance in the name of a `parentType`. // The `parentType` defaults to `Parent` when omitting the second parameter. const provideParent = (component: any, parentType?: any) => { return { provide: parentType || Parent, useExisting: forwardRef(() => component) }; };

And here's how you could use it with a different parent type:

下面的代码演示了如何使它添加一个不同类型的父级:

providers: [ provideParent(BethComponent, DifferentParent) ]

Break circularities with a forward class reference (forwardRef)

使用一个前向引用(forwardRef)来打破循环

The order of class declaration matters in TypeScript. You can't refer directly to a class until it's been defined.

在 TypeScript 里面,类声明的顺序是很重要的。如果一个类尚未定义,就不能引用它。

This isn't usually a problem, especially if you adhere to the recommended one class per file rule. But sometimes circular references are unavoidable. You're in a bind when class 'A' refers to class 'B' and 'B' refers to 'A'. One of them has to be defined first.

这通常不是一个问题,特别是当你遵循一个文件一个类规则的时候。 但是有时候循环引用可能不能避免。当一个类A 引用类 B,同时'B'引用'A'的时候,你就陷入困境了:它们中间的某一个必须要先定义。

The Angular forwardRef() function creates an indirect reference that Angular can resolve later.

Angular 的 forwardRef() 函数建立一个间接地引用,Angular 可以随后解析。

The Parent Finder sample is full of circular class references that are impossible to break.

Parent Finder是一个充满了无法解决的循环引用的例子

You face this dilemma when a class makes a reference to itself as does the AlexComponent in its providers array. The providers array is a property of the @Component decorator function which must appear above the class definition.

当一个类需要引用自身的时候,你面临同样的困境,就像在 AlexComponentprovdiers 数组中遇到的困境一样。 该 providers 数组是一个 @Component 装饰器函数的一个属性,它必须在类定义之前出现。

Break the circularity with forwardRef:

使用 forwardRef 来打破这种循环:

providers: [{ provide: Parent, useExisting: forwardRef(() => AlexComponent) }],