1377 lines
79 KiB
Plaintext
1377 lines
79 KiB
Plaintext
include ../_util-fns
|
||
|
||
:marked
|
||
Dependency Injection is a powerful pattern for managing code dependencies.
|
||
In this cookbook we will explore many of the features of Dependency Injection (DI) in Angular.
|
||
|
||
依赖注入是一个强大的管理代码依赖链的架构模式。在这个“食谱”中,我们会讨论Angular依赖注入的许多特性。
|
||
<a id="toc"></a>
|
||
:marked
|
||
## Table of contents
|
||
## 目录
|
||
|
||
[Application-wide dependencies](#app-wide-dependencies)
|
||
|
||
[应用程序全局依赖](#app-wide-dependencies)
|
||
|
||
[External module configuration](#external-module-configuration)
|
||
|
||
[外部模块设置](#external-module-configuration)
|
||
[*@Injectable* and nested service dependencies](#nested-dependencies)
|
||
|
||
[*@Injectable*和嵌套服务的依赖](#nested-dependencies)
|
||
|
||
[Limit service scope to a component subtree](#service-scope)
|
||
|
||
[限制服务作用范围到一个组件支树](#service-scope)
|
||
|
||
[Multiple service instances (sandboxing)](#multiple-service-instances)
|
||
|
||
[多个服务实例(sandboxing)](#multiple-service-instances)
|
||
|
||
[Qualify dependency lookup with *@Optional* and *@Host*](#qualify-dependency-lookup)
|
||
|
||
[使用*@Optional*和*@Host*装饰来认证依赖调用过程](#qualify-dependency-lookup)
|
||
|
||
[Inject the component's DOM element](#component-element)
|
||
|
||
[注入组件的DOM元素](#component-element)
|
||
|
||
[Define dependencies with providers](#providers)
|
||
|
||
[使用Provider定义依赖](#providers)
|
||
|
||
* [The *provide* function](#provide)
|
||
|
||
* [*provide*功能](#provide)
|
||
|
||
* [useValue - the *value provider*](#usevalue)
|
||
* [useClass - the *class provider*](#useclass)
|
||
* [useExisting - the *alias provider*](#useexisting)
|
||
* [useFactory - the *factory provider*](#usefactory)
|
||
|
||
[Provider token alternatives](#tokens)
|
||
* [class-interface](#class-interface)
|
||
* [OpaqueToken](#opaque-token)
|
||
|
||
[Inject into a derived class](#di-inheritance)
|
||
|
||
[Find a parent component by injection](#find-parent)
|
||
* [Find parent with a known component type](#known-parent)
|
||
* [Cannot find a parent by its base class](#base-parent)
|
||
* [Find a parent by its class-interface](#class-interface-parent)
|
||
* [Find a parent in a tree of parents (*@SkipSelf*)](#parent-tree)
|
||
* [A *provideParent* helper function](#provideparent)
|
||
|
||
[Break circularities with a forward class reference (*forwardRef*)](#forwardref)
|
||
|
||
[使用一个forward类引用(*forwardRef*)打破环状依赖](#forwardref)
|
||
|
||
:marked
|
||
**See the [live example](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)**
|
||
of the code supporting this cookbook.
|
||
获取本“食谱”的代码支持,**请看[在线例子](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)**。
|
||
.l-main-section
|
||
|
||
<a id="app-wide-dependencies"></a>
|
||
:marked
|
||
## Application-wide dependencies
|
||
## 应用程序全局依赖
|
||
Register providers for dependencies used throughout the application in the root application component, `AppComponent`.
|
||
|
||
在应用程序根组件`AppComponent`注册那些被应用程序全局使用的依赖providers。
|
||
In the following example, we import and register several services
|
||
(the `LoggerService`, `UserContext`, and the `UserService`)
|
||
in the `@Component` metadata `providers` array.
|
||
|
||
在下面的例子中,我们在`@Component`元数据的`providers`数组中,导入和注册了几个服务(`LoggerService`, `UserContext`和`UserService`)。
|
||
|
||
+makeExample('cb-dependency-injection/ts/app/app.component.ts','import-services','app/app.component.ts (excerpt)')(format='.')
|
||
:marked
|
||
All of these services are implemented as classes.
|
||
Service classes can act as their own providers which is why listing them in the `providers` array
|
||
is all the registration we need.
|
||
|
||
所有上面这些服务都是用类来实现的。服务类能充当它们自己的Providers,这就是为什么把它们列到一个`providers`数组里是唯一的注册要求。
|
||
.l-sub-section
|
||
:marked
|
||
A *provider* is something that can create or deliver a service.
|
||
Angular creates a service instance from a class provider by "new-ing" it.
|
||
Learn more about providers [below](#providers).
|
||
一个*provider*是用来新建或者送交服务的。Angular从一个类provider里面,通过“new-ing”来新建服务实例的。从[下面](#providers)学习更多关于provders的知识。
|
||
:marked
|
||
Now that we've registered these services,
|
||
Angular can inject them into the constructor of *any* component or service, *anywhere* in the application.
|
||
|
||
现在我们已经注册了这些服务,Angular能在应用程序的*任何地方*,将它们注入到*任何*组件和服务的构造函数里面。
|
||
+makeExample('cb-dependency-injection/ts/app/hero-bios.component.ts','ctor','app/hero-bios.component.ts (component constructor injection)')(format='.')
|
||
|
||
+makeExample('cb-dependency-injection/ts/app/user-context.service.ts','ctor','app/user-context.service.ts (service constructor injection)')(format='.')
|
||
|
||
<a id="external-module-configuration"></a>
|
||
.l-main-section
|
||
:marked
|
||
## External module configuration
|
||
## 外部模块设置
|
||
We can register _certain_ module providers when bootstrapping rather than in the root application component.
|
||
|
||
与其在应用程序根组件里面,我们可以在引导过程中注册_某些_模块providers。
|
||
|
||
We'd do this when we expect to select or configure external modules that support our application
|
||
but (a) aren't conceptually part of the application and (b) that we could change later without
|
||
altering the essential logic of the application.
|
||
|
||
当使用支持我们的应用程序,并满足下面两个条件外来组件时,我们应该这样做(在引导过程中注册):
|
||
a)在概念上不是我们程序的一部分
|
||
b)在未来,我们可能需要在不变化主要应用程序逻辑的情况下,更改或更换它。
|
||
|
||
For example, we might configure the Component Router with different
|
||
[location strategies](../guide/router.html#location-strategy) based on environmental factors.
|
||
The choice of location strategy doesn't matter to the application itself.
|
||
|
||
比如,我们可能用不同的[location strategies](../guide/router.html#location-strategy),根据不同的环境因数设置不同的组件路由。这个location strategy不直接影响应用程序本身。
|
||
|
||
We could sneak in a fake HTTP backend with sample data during development rather than
|
||
allow http calls to a remote server (that might not yet exist).
|
||
We'll switch to the real backend in production.
|
||
The application shouldn't know or care one way or the other.
|
||
|
||
在开发过程中,我们可以偷偷把一个假的带有例子数据的HTTP后端嵌入进来,来取代对一个远程服务器(可能还不存在)进行http查询。我们在产品发布时再切换到真正的后端。应用程序不需要知道,也不在乎哪个后端。
|
||
|
||
See both examples in the following `main.ts`
|
||
where we list their service providers in an array in the second parameter of the `bootstrap` method.
|
||
|
||
在下面`main.ts`的两个例子中,我们在`bootstrap`类方法的第二个参数的数组中,我们列出了它们的service providers(服务提供者)。
|
||
|
||
+makeExample('cb-dependency-injection/ts/app/main.ts','bootstrap','app/main.ts')(format='.')
|
||
|
||
a(id="injectable")
|
||
a(id="nested-dependencies")
|
||
.l-main-section
|
||
:marked
|
||
## *@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.
|
||
|
||
有时候一个服务依赖其他服务...(其他服务)可能依赖另外的服务。按正确的顺序来解析这些嵌套依赖也是框架工具(Angualar 2依赖注入)的工作。
|
||
在每一步,依赖的使用者只是在它的构造函数里简单地声明它需要什么,框架工具会做剩余的事情。
|
||
|
||
For example, we inject both the `LoggerService` and the `UserContext` in the `AppComponent`.
|
||
|
||
比如,我们在`AppComponent`里注入`LoggerService`和`UserContext`。
|
||
+makeExample('cb-dependency-injection/ts/app/app.component.ts','ctor','app/app.component.ts')(format='.')
|
||
|
||
:marked
|
||
The `UserContext` in turn has dependencies on both the `LoggerService` (again) and
|
||
a `UserService` that gathers information about a particular user.
|
||
|
||
`UserContext`有两个依赖`LoggerService`(再次)和负责获取特定用户信息的`UserService`。
|
||
|
||
+makeExample('cb-dependency-injection/ts/app/user-context.service.ts','injectables','user-context.service.ts (injection)')(format='.')
|
||
|
||
:marked
|
||
When Angular creates an`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 `new` one into existence.
|
||
|
||
当Angular新建一个`AppComponent`,依赖注入框架工具创建一个`LoggerService`的实例,和开始创建`UserContextService`。`UserContextService`需要框架工具已经有了的`LoggerService`和还没创建的`UserService`。
|
||
`UserService`没有其他依赖,所以依赖注入框架工具可以直接`new`一个实例。
|
||
|
||
The beauty of dependency injection is that the author of `AppComponent` didn't care about any of this.
|
||
The author simply declared what was needed in the constructor (`LoggerService` and `UserContextService`) and the framework did the rest.
|
||
|
||
依赖注入美丽的地方在于,`AppComponent`的作者不需要在乎这一切。作者只是在构造函数(`LoggerService`和`UserContextService`)里面简单地声明一下,框架工具来做剩下的工作。
|
||
|
||
Once all the dependencies are in place, the `AppComponent` displays the user information:
|
||
|
||
一旦所有依赖都准备好了,`AppComponent`显示用户信息:
|
||
figure.image-display
|
||
img(src="/resources/images/cookbooks/dependency-injection/logged-in-user.png" alt="Logged In User")
|
||
:marked
|
||
### *@Injectable()*
|
||
### *@Injectable()*
|
||
Notice the `@Injectable()`decorator on the `UserContextService` class.
|
||
|
||
请注意在`UserContextService`类里面的`@Injectable()`装饰器。
|
||
+makeExample('cb-dependency-injection/ts/app/user-context.service.ts','injectable','user-context.service.ts (@Injectable)')(format='.')
|
||
:marked
|
||
That decorator makes it possible for Angular to identify the types of its two dependencies, `LoggerService` and `UserService`.
|
||
|
||
该装饰器让Angular有能力辨认它的两个依赖 `LoggerService` 和 `UserService`。
|
||
|
||
Technically, the `@Injectable()`decorator is only _required_ for a service class that has _its own dependencies_.
|
||
The `LoggerService` doesn't depend on anything. The logger would work if we omitted `@Injectable()`
|
||
and the generated code would be slightly smaller.
|
||
|
||
技术上讲,这个`@Injectable()`装饰器只在一个服务类有_自己的依赖_的时候,才是_不可缺少_的。`LoggerService`不依赖任何东西。该日志在没有`@Injectable()`的时候应该也工作,生成的代码也小一些。
|
||
But the service would break the moment we gave it a dependency and we'd have to go back and
|
||
and add `@Injectable()` to fix it. We add `@Injectable()` from the start for the sake of consistency and to avoid future pain.
|
||
|
||
但是该服务在我们添加依赖给它的那一刻就会停止工作,要修复它,我们就必须要添加`@Injectable()`。为了保持一致性和防止将来的麻烦,我们从一开始就添加`@Injectable()`。
|
||
|
||
.alert.is-helpful
|
||
:marked
|
||
Although we recommend applying `@Injectable` to all service classes, do not feel bound by it.
|
||
Some developers prefer to add it only where needed and that's a reasonable policy too.
|
||
|
||
虽然我们推荐在所有服务中使用`@Injectable`,你不需要一定要这么做。一些开发者宁可在需要添加的地方才添加,这也是一个合理的策略。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
The `AppComponent` class had two dependencies as well but no `@Injectable()`.
|
||
It didn't need `@Injectable()` because that component class has the `@Component` decorator.
|
||
In Angular with TypeScript, a *single* decorator — *any* decorator — is sufficient to identify dependency types.
|
||
|
||
`AppComponent`类有两个依赖,但是没有`@Injectable()`。它不需要`@Injectable()`是因为组件类有`@Component`装饰器。
|
||
在用TypeScript的Angular(应用程序)里,一个 *单独的* 装饰器 — *任何* 装饰器 — 来辨识依赖类别就足够了。
|
||
<a id="service-scope"></a>
|
||
.l-main-section
|
||
:marked
|
||
## Limit service scope to a component subtree
|
||
## 限制服务作用范围到一个组件支树
|
||
All injected service dependencies are singletons meaning that,
|
||
for a given dependency injector ("injector"), there is only one instance of service.
|
||
|
||
所有注入的服务依赖都是单例(singletons),意思是,在任意一个依赖注入器("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 desireable.
|
||
Sometimes we want to restrict service availability to a particular region of the application.
|
||
|
||
这不一定总是想要的。有时候我们想要把服务的有效性限制到应用程序的一个特定区域。
|
||
|
||
We 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*.
|
||
Here we provide the `HeroService` to the `HeroesBaseComponent` by listing it in the `providers` array:
|
||
|
||
通过*在该分支的子级根部组件*中提供该服务, 我们能把一个注入服务的作用范围局限到一个应用程序结构的该*分支*中。
|
||
这里我们通过列入`providers`数列,在`HeroesBaseComponent`中提供了`HeroService`:
|
||
+makeExample('cb-dependency-injection/ts/app/sorted-heroes.component.ts','injection','app/sorted-heroes.component.ts (HeroesBaseComponent excerpt)')
|
||
:marked
|
||
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`实例,该实例只在该组件和其子组件(如果有)中可见。
|
||
|
||
We 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`。这样的结果是一个*不同的*该服务的实例,在一个*不同的*注入器内存在。
|
||
.l-sub-section
|
||
:marked
|
||
We 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`单例的例子,贯穿伴随例子代码,包括`HeroBiosComponent`, `HeroOfTheMonthComponent`和`HeroBaseComponent`。
|
||
这些组件中每个都有自己的`HeroService`实例,管理自己独立的英雄库。
|
||
|
||
.l-main-section
|
||
.alert.is-helpful
|
||
:marked
|
||
### 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开发者来说,这么多依赖注入知识可能是所有需要的全部知识。不一定都要更加复杂。
|
||
|
||
<a id="multiple-service-instances"></a>
|
||
.l-main-section
|
||
:marked
|
||
## Multiple service instances (sandboxing)
|
||
## 多个服务实例(sandboxing)
|
||
|
||
Sometimes we 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.
|
||
We 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.
|
||
We call this *sandboxing* because each service and component instance has its own sandbox to play in.
|
||
|
||
一个管理自己伴随组件实例状态的服务是个好例子。
|
||
对每个组件,我们都需要该服务的单独实例。每个服务有自己的工作-状态,与其他组件的服务-和-状态隔离。我们叫这个*sandboxing*,因为每个服务和组件实例都在自己的沙盒里运行。
|
||
|
||
<a id="hero-bios-component"></a>
|
||
Imagine a `HeroBiosComponent` that presents three instances of the `HeroBioComponent`.
|
||
|
||
想象一下,一个`HeroBioComponent`,显示三个`HeroBioComponent`的实例。
|
||
+makeExample('cb-dependency-injection/ts/app/hero-bios.component.ts','simple','ap/hero-bios.component.ts')
|
||
:marked
|
||
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`来对该英雄进行读取、缓存和执行其他持久性质的操作。
|
||
+makeExample('cb-dependency-injection/ts/app/hero-cache.service.ts','service','app/hero-cache.service.ts')
|
||
:marked
|
||
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`实例。
|
||
+makeExample('cb-dependency-injection/ts/app/hero-bio.component.ts','component','app/hero-bio.component.ts')
|
||
:marked
|
||
The parent `HeroBiosComponent` binds a value to the `heroId`.
|
||
The `ngOnInit` pass 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.
|
||
|
||
此父级`HeroBioComponent`绑定一个变量到`HeroId`。`ngOnInit`传递该`id`到服务,服务再获取和缓存英雄。`hero`属性的getter从服务里面获取缓冲的英雄,并在模板里面显示该数据绑定的属性。
|
||
|
||
Find this example in [live code](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)
|
||
and confirm that the three `HeroBioComponent` instances have their own cached hero data.
|
||
|
||
在[在线代码](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)找到这个例子,确认三个`HeroBioComponent`实例有自己拥有的缓冲的英雄数据。
|
||
figure.image-display
|
||
img(src="/resources/images/cookbooks/dependency-injection/hero-bios.png" alt="Bios")
|
||
|
||
a(id="optional")
|
||
a(id="qualify-dependency-lookup")
|
||
.l-main-section
|
||
:marked
|
||
## Qualify dependency lookup with *@Optional* and *@Host*
|
||
## 使用*@Optional*和*@Host*装饰来认证依赖调用过程
|
||
|
||
We learned that 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以该组件注入器开始,然后往上面以及的注入器树走,知道它找到第一个合适的provider。如果Angular不能再这个过程中找不到合适的依赖,它就抛出一个错误。
|
||
|
||
We *want* this behavior most of the time.
|
||
But sometimes we need to limit the search and/or accommodate a missing dependency.
|
||
We 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`装饰器将往上搜索的行为停止到*主持组件host component*
|
||
|
||
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.
|
||
We look at this second, more interesting case in our next example.
|
||
|
||
该主持组件一般为请求依赖的组件。但是当这个组件被投放到一个*父级*组件后,该父级组件变成了主持。我们考虑一会,更多有趣的案例还在后面的例子里。
|
||
|
||
### Demonstration
|
||
### 示范
|
||
The `HeroBiosAndContactsComponent` is a revision of the `HeroBiosComponent` that we looked at [above](#hero-bios-component).
|
||
|
||
该`HeroBiosAndContactsComponent`是[上面](#hero-bios-component)我们看过的`HeroBiosComponent`的修改版。
|
||
+makeExample('cb-dependency-injection/ts/app/hero-bios.component.ts','hero-bios-and-contacts','app/hero-bios.component.ts (HeroBiosAndContactsComponent)')
|
||
:marked
|
||
Focus on the template:
|
||
|
||
注意它的模板:
|
||
+makeExample('cb-dependency-injection/ts/app/hero-bios.component.ts','template')(format='.')
|
||
:marked
|
||
We've inserted a `<hero-contact>` element between the `<hero-bio>` tags.
|
||
Angular *projects* (*transcludes*) the corresponding `HeroContactComponent` into the `HeroBioComponent` view,
|
||
placing it in the `<ng-content>` slot of the `HeroBioComponent` template:
|
||
|
||
我们在`<hero-bio>`标签里插入了`<hero-contact>`元素。Angular*投放*(*transcludes*)对应的`HeroContactComponent`到`HeroBioComponent`视图里,将它放到`HeroBioComponent`模板的`<ng-content>`槽里面。
|
||
+makeExample('cb-dependency-injection/ts/app/hero-bio.component.ts','template','app/hero-bio.component.ts (template)')(format='.')
|
||
:marked
|
||
It looks like this, with the heroe's telephone number from `HeroContactComponent` projected above the hero description:
|
||
|
||
从`HeroContactComponent`来的英雄的电话号码被投放到上面的英雄说明里面,看起来像这样:
|
||
figure.image-display
|
||
img(src="/resources/images/cookbooks/dependency-injection/hero-bio-and-content.png" alt="bio and contact")
|
||
:marked
|
||
Here's the `HeroContactComponent` which demonstrates the qualifying decorators that we're talking about in this section:
|
||
|
||
下面是`HeroContactComponent`,示范了我们在本节一直在讨论的认证装饰器(Qualifying decorators):
|
||
+makeExample('cb-dependency-injection/ts/app/hero-contact.component.ts','component','app/hero-contact.component.ts')
|
||
:marked
|
||
Focus on the constructor parameters
|
||
请注意看构造函数的参数。
|
||
+makeExample('cb-dependency-injection/ts/app/hero-contact.component.ts','ctor-params','app/hero-contact.component.ts')(format='.')
|
||
:marked
|
||
The `@Host()` function decorating the `_heroCache` property ensures that
|
||
we get a reference to the cache service from the parent `HeroBioComponent`.
|
||
Angular throws if the parent lacks that service, even if a component higher in the component tree happens to have that service.
|
||
|
||
`@Host()`函数装饰`_heroCache`属性,确保我们从其父级`HeroBioComponent`得到一个缓冲服务的引用。如果该父级不存在这个服务,Angular则抛错,就算组件树里再上级有一个组件拥有这个服务(Angular也抛错)。
|
||
A second `@Host()` function decorates the `_loggerService` property.
|
||
We know 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`属性,我们知道在应用程序中,只有一个`LoggerService`实例,在`AppComponent`级别提供的。该主持`HeroBioComponent`没有自己的`LoggerService`provider。
|
||
|
||
Angular would throw an error if we 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,并继续改组件的执行。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
We'll come back to the `elementRef` property shortly.
|
||
|
||
我们将很快回到`elementRef`属性。
|
||
:marked
|
||
Here's the `HeroBiosAndContactsComponent` in action.
|
||
下面是`HeroBiosAndContactsComponent`的执行结果:
|
||
figure.image-display
|
||
img(src="/resources/images/cookbooks/dependency-injection/hero-bios-and-contacts.png" alt="Bios with contact into")
|
||
:marked
|
||
If we 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 gratuituous "!!!", indicating that the logger was found.
|
||
|
||
如果我们注释掉`@Host()`装饰器,Angular沿着注入器树就会往上走,直到在`AppComponent`找到该日志服务。这个日志服务的逻辑加入进来,英雄显示更新,表明日志服务已找到。
|
||
figure.image-display
|
||
img(src="/resources/images/cookbooks/dependency-injection/hero-bio-contact-no-host.png" alt="Without @Host")
|
||
:marked
|
||
On the other hand, if we restore the `@Host()` decorator and comment out `@Optional`,
|
||
the application fails for lack of the required logger at the host component level.
|
||
<br>
|
||
`EXCEPTION: No provider for LoggerService! (HeroContactComponent -> LoggerService)`
|
||
|
||
另一方面,如果我们恢复`@Host()`装饰器,注释掉`@Optional`,我们的应用程序会(运行)失败,因为它在主持组件级别找不到需要的日志服务。
|
||
<br>
|
||
`EXCEPTION: No provider for LoggerService! (HeroContactComponent -> LoggerService)`
|
||
|
||
<a id="component-element"></a>
|
||
:marked
|
||
## Inject the component's element
|
||
## 注入一个组件的元素
|
||
|
||
On occasion we might need to access a component's corresponding DOM element.
|
||
Although we strive to avoid it, many visual effects and 3rd party tools (such as jQuery)
|
||
require DOM access.
|
||
|
||
偶尔,我们可能需要访问一个组件对应的DOM元素。我们力争避免这样做,但是很多视觉效果和第三方工具需要访问DOM。
|
||
|
||
To illustrate, we've written a simplified version of the `HighlightDirective` from
|
||
the [Attribute Directives](../guide/attribute-directives.html) chapter.
|
||
|
||
为了示范,我们在[属性指令Attribute Directives](../guide/attribute-directives.html)的`HighlightDirective`的基础上,编写了一个简化版本。
|
||
+makeExample('cb-dependency-injection/ts/app/highlight.directive.ts','','app/highlight.directive.ts')
|
||
:marked
|
||
The directive sets the background to a highlight color when the user mouses over the
|
||
DOM element to which it is applied.
|
||
|
||
当用户鼠标移到DOM元素是,该指令将该元素的背景设置为一个高亮颜色。
|
||
|
||
Angular set 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`代表了(主持host)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`属性使用到两个`<div>`标签里,一个没有赋值,一个赋值了颜色。
|
||
+makeExample('cb-dependency-injection/ts/app/app.component.html','highlight','app/app.component.html (highlight)')(format='.')
|
||
:marked
|
||
The following image shows the effect of mousing over the `<hero-bios-and-contacts>` tag.
|
||
下图显示了鼠标移到`<hero-bios-and-contacts>`标签的效果:
|
||
figure.image-display
|
||
img(src="/resources/images/cookbooks/dependency-injection/highlight.png" alt="Highlighted bios")
|
||
:marked
|
||
|
||
<a id="providers"></a>
|
||
.l-main-section
|
||
:marked
|
||
## Define dependencies with providers
|
||
## 使用providers来定义依赖
|
||
In this section we learn to write providers that deliver dependent services.
|
||
|
||
在这个部分,我们学习如何编写providers来提供依赖服务。
|
||
|
||
### Background
|
||
### 背景知识
|
||
We get a service from a dependency injector by giving it a ***token***.
|
||
|
||
我们从一个依赖注入器,通过给他一个***令牌(token)***来获取一个服务。
|
||
We usually let Angular handle this transaction for us 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把该令牌传递给注入器,然后把结果赋给参数。下例是一个典型的例子:
|
||
|
||
+makeExample('cb-dependency-injection/ts/app/hero-bios.component.ts','ctor','app/hero-bios.component.ts (component constructor injection)')(format='.')
|
||
:marked
|
||
Angular asks the injector for the service associated with the `LoggerService` and
|
||
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.
|
||
It 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*.
|
||
|
||
注入器在哪儿得到的返回值?
|
||
它可能在自己内部容器里已经有该值了。
|
||
如果它没有,他可能能在***provider***的帮助下新建一个。
|
||
一个*provider*是一个通过指令派送对应的服务方法。
|
||
.l-sub-section
|
||
:marked
|
||
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](#optional).
|
||
|
||
如果注入器对请求的令牌没有一个对应的provider,它便将这个请求交给它父级的注入器,这个过程反复从夫,知道没有更多注入器位置。
|
||
如果搜索无用,注入器便抛出一个错误...除非这个请求是[optional](#optional)。
|
||
|
||
Let's return our attention to providers themselves.
|
||
让我们把注意力转回到providers。
|
||
:marked
|
||
A new injector has no providers.
|
||
|
||
一个新的注入器没有providers。
|
||
Angular initializes the injectors it creates with some providers it cares about.
|
||
We have to register our _own_ application providers manually,
|
||
usually in the `providers` array of the `Component` or `Directive` metadata:
|
||
|
||
Angular初始化一些他自己建立和需要的注入器,附带一些providers。我们必须要亲自手动注册属于_自己_的providers,通常在`组件`或者`指令`的元数据里面的`providers`数组(里面注册)。
|
||
+makeExample('cb-dependency-injection/ts/app/app.component.ts','providers','app/app.component.ts (providers)')
|
||
:marked
|
||
### Defining providers
|
||
### 定义providers
|
||
|
||
The simple class provider is the most typical by far.
|
||
We mention the class in the `providers` array and we're done.
|
||
|
||
目前一个简单的类provider是最典型的例子。
|
||
我们在`providers`的数值里面提到该类就行了。
|
||
+makeExample('cb-dependency-injection/ts/app/hero-bios.component.ts','class-provider','app/hero-bios.component.ts (class provider)')(format='.')
|
||
:marked
|
||
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.
|
||
We need other ways to deliver dependency values and that means we need other ways to specify a provider.
|
||
|
||
这就是那么简单,因为最常见的被注入的服务是一个类的实例。但是,不是所有的依赖能在能建立一个类的新实例就够了的。我们需要其他的提交依赖值得方法,也就是说我们需要其他方法来制定一个provider。
|
||
|
||
The `HeroOfTheMonthComponent` example demonstrates many of the alternatives and why we need them.
|
||
|
||
`HeroOfTheMonthComponent`例子示范了一些备择方案,揭示了为什么我们需要他们。
|
||
figure.image-display
|
||
img(src="/resources/images/cookbooks/dependency-injection/hero-of-month.png" alt="Hero of the month" width="300px")
|
||
:marked
|
||
It's visually simple: a few properties and the output of a logger. The code behind it gives us plenty to talk about.
|
||
|
||
它看起来很简单:一些属性和一个日志output。但是后面的代码给我们很多议论点。
|
||
+makeExample('cb-dependency-injection/ts/app/hero-of-the-month.component.ts','hero-of-the-month','hero-of-the-month.component.ts')
|
||
|
||
.l-main-section
|
||
a(id='provide')
|
||
:marked
|
||
#### The *provide* function
|
||
#### *provide*函数
|
||
|
||
The imported Angular `provide` function creates an instance of
|
||
the Angular [Provider](../api/core/Provider-class.html) class.
|
||
|
||
被导入的Angular `provide`函数新建一个Angular [Provider](../api/core/Provider-class.html)类的实例。
|
||
|
||
The `provide` function takes a *token* and a *definition object*.
|
||
The *token* is usually a class but [it doesn't have to be](#tokens).
|
||
|
||
该`provide`函数需要一个*令牌*和一个*定义对象*。该*令牌*通常是一个类,但是[他不是一定是](#tokens)
|
||
|
||
The *definition* object has one main property, (e.g. `useValue`) that indicates how the provider
|
||
should create or return the provided value.
|
||
|
||
该*定义*项目有一个主要属性,(即为`userValue`)来标识该provider应该怎么新建和返回被提供的结果。
|
||
|
||
.l-main-section
|
||
a(id='usevalue')
|
||
:marked
|
||
#### useValue - the *value provider*
|
||
#### useValue - *值provider*
|
||
|
||
Set the `useValue` property to a ***fixed value*** that the provider can return as the dependency object.
|
||
|
||
把一个***固定的值**,也就是该provider可以当作依赖对象返回的值,赋给`userValue`属性。
|
||
|
||
Use this technique to provide *runtime configuration constants* such as web-site base addresses and feature flags.
|
||
We often use a *value provider* in a unit test to replace a production service with a fake or mock.
|
||
|
||
使用该技巧来提供*运行时间设置常量*,比如网站的基础地址和功能标志等。我们通常在单元测试中使用一个*value provider*,用一个假的或模仿的(服务)来取代一个成品服务。
|
||
|
||
The `HeroOfTheMonthComponent` example has two *value providers*.
|
||
The first provides an instance of the `Hero` class;
|
||
the second specifies a literal string resource:
|
||
|
||
`HeroOfTheMonthComponent`例子有两个*value providers*。
|
||
第一个提供了一个`Hero`类的实例;第二个指定了一个文字字符串资源:
|
||
+makeExample('cb-dependency-injection/ts/app/hero-of-the-month.component.ts','use-value')(format='.')
|
||
:marked
|
||
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`provider令牌是一个类,这很合理,因为提供的结果是一个`Hero`,而且使用这个被注入英雄的组件也想要知道这个类型信息。
|
||
|
||
The `TITLE` provider token is *not a class*.
|
||
It's a special kind of provider lookup key called an [OpaqueToken](#opaquetoken).
|
||
We often use an `OpaqueToken` when the dependency is a simple value like a string, a number, or a function.
|
||
|
||
`TITLE` provider令牌*不是一个类*。它是一个特别类型的provider查询键,名叫[OpaqueToken](#opaquetoken).
|
||
|
||
The value of a *value provider* must be defined *now*. We 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:
|
||
|
||
一个*value provider*的值必须要*立刻*定义。我们不能过后定义它的值。很显然,该标题字符串立刻可用。
|
||
该例中的`someHero`变量是于早先,在下面的文件里面定义的:
|
||
+makeExample('cb-dependency-injection/ts/app/hero-of-the-month.component.ts','some-hero')
|
||
:marked
|
||
The other providers create their values *lazily* when they're needed for injection.
|
||
|
||
其他providers只在注入需要他们的时候才创建他们值 - *延迟加载*
|
||
|
||
.l-main-section
|
||
a(id='useclass')
|
||
:marked
|
||
#### useClass - the *class provider*
|
||
#### useClass - *类provider*
|
||
|
||
The `useClass` provider creates and returns new instance of the specified class.
|
||
|
||
`userClass` provider创建并返回一个特定类的新实例。
|
||
|
||
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.
|
||
|
||
使用此项技术来为公共或默认类***提供候选实现***。该候选方法能实现一个不同的策略,拓展默认类,或者在测试的时候假冒正真类的行为。
|
||
|
||
We see two examples in the `HeroOfTheMonthComponent`:
|
||
|
||
请看下面`HeroOfTheMonthComponent`里的两个例子:
|
||
+makeExample('cb-dependency-injection/ts/app/hero-of-the-month.component.ts','use-class')(format='.')
|
||
:marked
|
||
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 injection token.
|
||
We wrote it in this long form to de-mystify the preferred short form.
|
||
|
||
第一个provider是*无语法糖的*,是从典型的情况扩展的,一般被新建的类(`HeroService`)同时也是该provider的注入令牌。
|
||
我们使用长的形式来编写它来解释首选的短形式。
|
||
|
||
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.
|
||
|
||
第二个provider使用`DateLoggerService`来满足`LoggerService`。该`LoggerService`在`AppComponent`级别已经被注册。当_这个组件_要求`LoggerService`的时候,取而代之,它得到的是`DateLoggerService`服务。
|
||
.l-sub-section
|
||
:marked
|
||
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`实例。
|
||
:marked
|
||
The `DateLoggerService` inherits from `LoggerService`; it appends the current date/time to each message:
|
||
|
||
`DateLoggerService`从`LoggerService`继承;它把当前的日期/时间附加到每条信息上。
|
||
+makeExample('cb-dependency-injection/ts/app/date-logger.service.ts','date-logger-service','app/date-logger.service.ts')(format='.')
|
||
|
||
.l-main-section
|
||
a(id='useexisting')
|
||
:marked
|
||
#### useExisting - the *alias provider*
|
||
#### useExisting - *别名provider*
|
||
|
||
The `useExisting` provider maps one token to another.
|
||
In effect, the first token is an ***alias*** for the service associated with second token,
|
||
creating ***two ways to access the same service object***.
|
||
|
||
使用`useExisting` provider来把一个令牌映射到另一个。实际上,第一个令牌是第二个令牌所对应的服务的一个***别名***,构成***对一个服务对象的两种访问方法***。
|
||
+makeExample('cb-dependency-injection/ts/app/hero-of-the-month.component.ts','use-existing')
|
||
:marked
|
||
Narrowing an API through an aliasing interface is _one_ important use case for this technique.
|
||
We're aliasing for that very purpose here.
|
||
Imagine that the `LoggerService` had a large API (it's actually only three methods and a property).
|
||
We want to shrink that API surface to just the two members exposed by the `MinimalLogger` [*class-interface*](#class-interface):
|
||
|
||
通过使用别名接口,把一个API变窄,是_一个_很重要的该技术的使用例子。我们在这里就是为了这个目的使用了别名。
|
||
想象一下如果`LoggerService`有个很大的API(它其实只有三个方法,一个属性),我们通过使用`MinimalLogger`[*类-接口*](#class-interface)别名要把这个API界面缩小到只暴露两个成员:
|
||
|
||
+makeExample('cb-dependency-injection/ts/app/date-logger.service.ts','minimal-logger','app/date-logger.service.ts (MinimalLogger)')(format='.')
|
||
:marked
|
||
The constructor's `logger` parameter is typed as `MinimalLogger` so only its two members are visible in TypeScript:
|
||
|
||
构造函数的`logger`参数是一个`MinimalLogger`类型,所有它只有两个成员在TypeScript里面可见:
|
||
figure.image-display
|
||
img(src="/resources/images/cookbooks/dependency-injection/minimal-logger-intellisense.png" alt="MinimalLogger restricted API")
|
||
:marked
|
||
Angular actually sets the `logger` parameter to the injector's full version of the `LoggerService`
|
||
which happens to be the `DateLoggerService` thanks to the override provider registered previously via `useClass`.
|
||
The following image, which displays the logging date, confirms the point:
|
||
|
||
Angular实际上设置`logger`参数给注入器里的`LoggerService`的完整版本,该完整版本被`DateLoggerService`所取代,多谢在之前provder注册里使用`useClass`。
|
||
在下面的图片中,显示了日志日期,确认了这点:
|
||
figure.image-display
|
||
img(src="/resources/images/cookbooks/dependency-injection/date-logger-entry.png" alt="DateLoggerService entry" width="300px")
|
||
|
||
.l-main-section
|
||
a(id='usefactory')
|
||
:marked
|
||
#### useFactory - the *factory provider*
|
||
#### useFactory - *factory provider*
|
||
|
||
The `useFactory` provider creates a dependency object by calling a factory function
|
||
as seen in this example.
|
||
|
||
`useFactory` provider通过调用一个在下例看到的factory函数来新建一个依赖对象。
|
||
+makeExample('cb-dependency-injection/ts/app/hero-of-the-month.component.ts','use-factory')
|
||
:marked
|
||
Use this technique to ***create a dependency object***
|
||
with a factory function whose inputs are some ***combination of injected services and local state***.
|
||
|
||
使用这个技巧,利用一个包含了一些***依赖服务和本地状态***的输入的factory函数,来***建立一个依赖对象***。
|
||
|
||
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.
|
||
We execute `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`自己provider factory函数。真正的provider factory函数是一个`runnersUpFactory`返回的函数。
|
||
|
||
+makeExample('cb-dependency-injection/ts/app/runners-up.ts','factory-synopsis','runners-up.ts (excerpt)')(format='.')
|
||
:marked
|
||
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`值是注入器使用*令牌*来提供的factory函数依赖。
|
||
|
||
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`参数里面。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
The function retrieves candidate heroes from the `HeroService`,
|
||
takes `2` of them to be the runners-up, and returns their concatenated names.
|
||
Look at the [live example](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)
|
||
for the full source code.
|
||
|
||
该函数从`HeroService`获取英雄参赛者,从中取`2`个作为亚军,并把他们的名字合并起来。亲到[在线例子](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)看全部原代码。
|
||
|
||
|
||
a(id="tokens")
|
||
.l-main-section
|
||
:marked
|
||
## Provider token alternatives: the *class-interface* and *OpaqueToken*
|
||
## 可选Provider令牌:**类-接口*和*OpaqueToken*
|
||
|
||
Angular dependency injection is easiest when the provider *token* is a class
|
||
that is also the type of the returned dependency object (what we 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 our next section.
|
||
|
||
但是令牌不一定非要是类,就算它是一个类,它也不一定非要是返回对象相同类型的类。这是我们下一节的课题。
|
||
|
||
<a id="class-interface"></a>
|
||
### class-interface
|
||
### 类-接口
|
||
In the previous *Hero of the Month* example, we used the `MinimalLogger` class
|
||
as the token for a provider of a `LoggerService`.
|
||
|
||
在前面的*每月英雄*的例子中,我们用`MinimalLogger`类当做`LoggerService` provider的令牌。
|
||
|
||
+makeExample('cb-dependency-injection/ts/app/hero-of-the-month.component.ts','use-existing')
|
||
:marked
|
||
The `MinimalLogger` is an abstract class.
|
||
|
||
该`MinimalLogger`是一个抽象类。
|
||
+makeExample('cb-dependency-injection/ts/app/date-logger.service.ts','minimal-logger')(format='.')
|
||
:marked
|
||
We usually inherit from an abstract class.
|
||
But `LoggerService` doesn't inherit from `MinimalLogger`. *No class* inherits from it.
|
||
Instead, we use it like an interface.
|
||
|
||
我们通常从一个抽象类继承。但是`LoggerService`并不继承`MinimalLogger`。*没有类*继承它。取而代之,我们把它当接口来使用。
|
||
|
||
Look again at the declaration for `DateLoggerService`
|
||
|
||
请再看`DateLoggerService`的声明。
|
||
+makeExample('cb-dependency-injection/ts/app/date-logger.service.ts','date-logger-service-signature')(format='.')
|
||
:marked
|
||
`DateLoggerService` inherits (extends) from `LoggerService`, not `MinimalLogger`.
|
||
The `DateLoggerService` *implements* `MinimalLogger` as if `MinimalLogger` were an *interface*.
|
||
|
||
`DateLoggerService`继承(延续)`LoggerService`,不是`MinimalLogger`。该`DateLoggerService`*实现*`MinimalLogger`,就像`MinimalLogger`是一个接口。
|
||
|
||
We call a class used in this way a ***class-interface***.
|
||
The key benefit of a *class-interface* is that we can get the strong-typing of an interface
|
||
and we can ***use it as a provider token*** in the same manner as a normal class.
|
||
|
||
我们称这种用法的类叫做*类-接口*。它关键的好处是一个*类-接口*给我们提供了一个接口的强类型,同时,我们能想正常类一样***使用它作为一个provider令牌***。
|
||
|
||
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.
|
||
The `MinimalLogger` defines just two of the `LoggerClass` members.
|
||
|
||
一个***类-接口***应该*只*定义它的消费者容许调用的成员。这样一个窄的接口帮助我们分离该类的具体类实例和他的消费者。
|
||
该`MinimalLogger`只定义了两个`LoggerClass`的成员。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
#### Why *MinimalLogger* is a class and not an interface
|
||
We 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.
|
||
|
||
### 为什么*MinimalLogger*是一个类而不是一个接口?因为我们不能把一个接口当做一个provider的令牌,因为接口不是JavaScript对象。
|
||
它们只存在在TypeScript的设计空间里。它们会在被编译到JavaScript之后消失。
|
||
|
||
A provider token must be a real JavaScript object of some kind:
|
||
a function, an object, a string ... a class.
|
||
|
||
一个provider令牌必须是一个真实的JavaScript对象,比如:一个函数,一个对象,一个字符串 ...一个类。
|
||
|
||
Using a class as an interface gives us the characteristics of an interface in a JavaScript object.
|
||
|
||
把一个类当做接口使用,为我们在一个JavaScript对象上提供了接口的特征。
|
||
|
||
The minimize memory cost, the class should have *no implementation*.
|
||
The `MinimalLogger` transpiles to this unoptimized, pre-minified JavaScript:
|
||
|
||
为了节省内存费用,该类不应该有*执行*。该`MinimalLogger`翻译到一个下面这个没有优化的,最小化之前的JavaScript:
|
||
+makeExample('cb-dependency-injection/ts/app/date-logger.service.ts','minimal-logger-transpiled')(format='.')
|
||
:marked
|
||
It never grows larger no matter how many members we add *as long as they are typed but not implemented*.
|
||
|
||
*只要我们不实现它*,不管我们添加多少成员,它永远不会增长大小。
|
||
|
||
a(id='opaque-token')
|
||
:marked
|
||
### OpaqueToken
|
||
### OpaqueToken
|
||
|
||
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 `OpaqueToken` has these characteristics.
|
||
We encountered them twice in the *Hero of the Month* example,
|
||
in the *title* value provider and in the *runnersUp* factory provider.
|
||
|
||
`OpaqueToken`具有这些特征。我们在*Hero of the Month*例子中遇见它们两次,一个是*title*的值,一个是*runnersUp* factory provider。
|
||
+makeExample('cb-dependency-injection/ts/app/hero-of-the-month.component.ts','provide-opaque-token')(format='.')
|
||
:marked
|
||
We created the `TITLE` token like this:
|
||
|
||
我们这样创建该`TITLE`令牌:
|
||
+makeExample('cb-dependency-injection/ts/app/hero-of-the-month.component.ts','opaque-token')(format='.')
|
||
|
||
|
||
a(id="di-inheritance")
|
||
.l-main-section
|
||
:marked
|
||
## Inject into a derived class
|
||
## 注入一个衍生类
|
||
|
||
We must take care when writing a component that inherits from another component.
|
||
If the base component has injected dependencies,
|
||
we 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`衍生,显示一个*被排序*的英雄列表。
|
||
|
||
figure.image-display
|
||
img(src="/resources/images/cookbooks/dependency-injection/sorted-heroes.png" alt="Sorted Heroes")
|
||
:marked
|
||
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`,用来得到英雄,并将它们按照数据库返回的顺序显示出来。
|
||
|
||
+makeExample('cb-dependency-injection/ts/app/sorted-heroes.component.ts','heroes-base','app/sorted-heroes.component.ts (HeroesBaseComponent)')
|
||
.l-sub-section
|
||
:marked
|
||
We strongly prefer simple constructors. 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 we call the `HeroService` from within the `ngOnInit` rather than the constructor.
|
||
|
||
我们强烈的推荐简单的结构函数。它们应该只用来初始化变量。这个规则让组件在测试环境中被安全的构造,免除他可能要去做一些非常戏剧化的动作(比如连接服务)的担心。
|
||
这就是为什么我们在`ngOnInit`里面调用`HeroService`,而不是在构造函数中。
|
||
|
||
We explain the mysterious `_afterGetHeroes` below.
|
||
|
||
我们在下面解释这个神秘的`_afterGetHeroes`。
|
||
:marked
|
||
Users want to see the heroes in alphabetical order.
|
||
Rather than modify the original component, we sub-class it and create a
|
||
`SortedHeroesComponent` that sorts the heroes before presenting them.
|
||
The `SortedHeroesComponent` lets the base class fetch the heroes.
|
||
(we said it was contrived).
|
||
|
||
用户希望看到英雄按字母顺序排序。与其修改原始的组件,我们衍生它,建立一个`SortedHeroesComponent`,用来在展示英雄之前进行排序。
|
||
该`SortedHeroesComponent`让基本类来获取英雄。(我们说过这个是人为的)。
|
||
|
||
Unfortunately, Angular cannot inject the `HeroService` directly into the base class.
|
||
We must provide the `HeroService` again for *this* component,
|
||
then pass it down to the base class inside the constructor.
|
||
|
||
可惜,Angular不能直接在基本类里直接注入`HeroService`。我们必须在*这个*组件里再次提供`HeroService`,然后通过构造函数传给基础类。
|
||
|
||
+makeExample('cb-dependency-injection/ts/app/sorted-heroes.component.ts','sorted-heroes','app/sorted-heroes.component.ts (SortedHeroesComponent)')
|
||
:marked
|
||
Now take note of the `_afterGetHeroes` method.
|
||
Our first instinct was 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 we'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*.
|
||
|
||
这些复杂性强调了*避免使用组件衍生*(的潜规则)。
|
||
|
||
a(id="find-parent")
|
||
.l-main-section
|
||
:marked
|
||
## Find a parent component by injection
|
||
## 通过注入来找到一个父级组件
|
||
Application components often need to share information.
|
||
We prefer the more loosely coupled techniques such as data binding and service sharing.
|
||
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
|
||
(checkout `Query`, `QueryList`, `ViewChildren`, and `ContentChildren`).
|
||
|
||
有一个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,
|
||
we can use Angular dependency injection to reach a parent component.
|
||
|
||
没有公共API来获取一个父级组件的引用。但是因为每个组件实例都被加到了一个注入器的容器中,我们能使用Angular依赖注入来抓住一个父级组件。
|
||
|
||
This section describes some techniques for doing that.
|
||
|
||
该节描述了怎么做这个的技巧。
|
||
|
||
<a id="known-parent"></a>
|
||
### Find a parent component of known type
|
||
|
||
### 找到一个已知类型的父级组件
|
||
|
||
We use standard class injection to acquire a parent component whose type we know.
|
||
|
||
我们使用一个标准类注入来获取一个已知类型的父级组件。
|
||
|
||
In the following example, the parent `AlexComponent` has several children including a `CathyComponent`:
|
||
|
||
在下面的例子中,父级`AlexComponent`有几个子级,包括一个`CathyComponent`:
|
||
a(id='alex')
|
||
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','alex-1','parent-finder.component.ts (AlexComponent v.1)')(format='.')
|
||
:marked
|
||
*Cathy* reports whether or not she has access to *Alex*
|
||
after injecting an `AlexComponent` into her constructor:
|
||
|
||
在注入一个*AlexComponent`进来后,*Cathy*报告她是否对*Alex*有访问权:
|
||
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','cathy','parent-finder.component.ts (CathyComponent)')(format='.')
|
||
:marked
|
||
We added the [@Optional](#optional) qualifier for safety but
|
||
the [live example](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)
|
||
confirms that the `alex` parameter is set.
|
||
|
||
为了安全,我们添加了[@Optional](#optional)装饰器,但是[在线例子](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)确认了`alex`参数被设置了。
|
||
|
||
<a id="base-parent"></a>
|
||
### Cannot find a parent by its base class
|
||
### 不能通过它的基本类找到一个父级
|
||
|
||
What if we do *not* 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 sound (cough) business reasons, this news component makes frequent calls
|
||
directly into its parent instrument as changing market data stream by.
|
||
|
||
一个可重复使用的组件可能是多个组件的子级。想想一个用来渲染金融工具头条新闻的组件。为了合理(咳嗽)的商业理由,该新闻组件在实时变化的市场数据流过时要频繁的直接调用其父级工具
|
||
|
||
The app probably defines more than a dozen financial instrument components.
|
||
If we're lucky, they all implement the same base class
|
||
whose API our `NewsComponent` understands.
|
||
|
||
该应用程序可能定义多于一打的金融工具组件。如果我们幸运,它们可能实现同一个基本类,其API是我们`NewsComponent`组件明白的。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
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 we could look for.
|
||
|
||
找一个实现一个接口的组件会好一些。但是这不可能,因为TypeScript的借口在编译成JavaScript以后就消失了,JavaScript不支持接口。我们没有东西可查。
|
||
:marked
|
||
We're not claiming this is good design.
|
||
We are asking *can a component inject its parent via the parent's base class*?
|
||
|
||
我们没有说这是一个好的设计。我们在问*一个组件是否能通过它父级的基本类来注入它的父级呢*?
|
||
|
||
The sample's `CraigComponent` explores this question. [Looking back](#alex)
|
||
we see that the `Alex` component *extends* (*inherits*) from a class named `Base`.
|
||
|
||
`CraigComponent`例子探究了这个问题。[往回看]{#alex},我们看到`Alex`组件从一个叫`Base`的类*延伸*(*衍生*)。
|
||
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','alex-class-signature','parent-finder.component.ts (Alex class signature)')(format='.')
|
||
:marked
|
||
The `CraigComponent` tries to inject `Base` into its `alex` constructor parameter and reports if it succeeded.
|
||
|
||
`CraigComponent`试图注入`Base`到它的`alex`构造函数参数,并报告是否成功。
|
||
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','craig','parent-finder.component.ts (CraigComponent)')(format='.')
|
||
:marked
|
||
Unfortunately, this does not work.
|
||
The [live example](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)
|
||
confirms that the `alex` parameter is null.
|
||
*We cannot inject a parent by its base class.*
|
||
|
||
可喜,这样不行。[在线例子](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)确认了`alex`参数是null。
|
||
*我们不能通过基本类注入父级*
|
||
|
||
<a id="class-interface-parent"></a>
|
||
### Find a parent by its class-interface
|
||
### 通过类-接口找到一个父级
|
||
|
||
We can find a parent component with a [class-interface](#class-interface).
|
||
|
||
我们能通过[类-接口](#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 we could inject *Alex* into *Carol* [earlier](#known-parent).
|
||
|
||
请记住Angular总是从它自己的注入器添加一个组件实例;这就是为什么在[之前](#known-parent)我们可以*Alex*注入到*Carol*。
|
||
|
||
We write an [*alias provider*](#useexisting) — a `provide` function 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`:
|
||
|
||
我们编写一个[*别名provider*](#useexisting) &mdash;一个拥有`useExisting`定义的`provide`函数 —
|
||
它新建一个*可选的*方法来注入一样的组件实例,并添加这个provider到`AlexComponent`的`@Component`元数据里的`providers`数组。
|
||
a(id="alex-providers")
|
||
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','alex-providers','parent-finder.component.ts (AlexComponent providers)')(format='.')
|
||
:marked
|
||
[Parent](#parent-token) is the provider's *class-interface* token.
|
||
The [*forwardRef*](#forwardref) breaks the circular reference we just created by having the `AlexComponent` refer to itself.
|
||
|
||
[父级](#parent-token)是该provider的*类-接口*令牌。我们刚通过在`AlexComponent`引用了自己的建立了一个引用循环, 这个[*forwardRef*](#forwardRef)打破了该循环。
|
||
*Carol*, the third of *Alex*'s child components, injects the parent into its `parent` parameter, the same way we've done it before:
|
||
|
||
*Carol*,该*Alex*子级组件的第三个组件,注入了父级到自己的`parent`参数,和我们之前做的一样:
|
||
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','carol-class','parent-finder.component.ts (CarolComponent class)')(format='.')
|
||
:marked
|
||
Here's *Alex* and family in action:
|
||
|
||
下面是*Alex*和其家庭的运行结果:
|
||
figure.image-display
|
||
img(src="/resources/images/cookbooks/dependency-injection/alex.png" alt="Alex in action")
|
||
|
||
a(id="parent-tree")
|
||
:marked
|
||
### Find the parent in a tree of parents
|
||
### 通过父级树找到父级
|
||
|
||
Imagine one branch of a component hierarchy: *Alice* -> *Barry* -> *Carol*.
|
||
Both *Alice* and *Barry* implement the `Parent` *class-interface*.
|
||
|
||
想象一下一个组件层次的一个分支为:*Alice* -> *Barry* -> *Carol*。
|
||
*Alice*和*Barry*两个都实现`父级`*类-接口*。
|
||
|
||
*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*的父级。这个意味着它必须同时*注入*`父级`*类-接口*来得到*Alice*,和*提供*一个`父级`来满足*Carol*。
|
||
|
||
Here's *Barry*:
|
||
下面是*Barry*:
|
||
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','barry','parent-finder.component.ts (BarryComponent)')(format='.')
|
||
:marked
|
||
*Barry*'s `providers` array looks just like [*Alex*'s](#alex-providers).
|
||
If we're going to keep writing [*alias providers*](#useexisting) like this we should create a [helper function](#provideparent).
|
||
|
||
*Barry*的`providers`数组看起来像[*Alex*的](#alex-providers).
|
||
如果我们一直要像这样编写[*别名providers*](#useexisting)的话,我们应该建立一个[帮助函数](#provideparent)。
|
||
|
||
For now, focus on *Barry*'s constructor:
|
||
眼下,集中主意*Barry*的构造函数:
|
||
+makeTabs(
|
||
'cb-dependency-injection/ts/app/parent-finder.component.ts, cb-dependency-injection/ts/app/parent-finder.component.ts',
|
||
'barry-ctor, carol-ctor',
|
||
'Barry\'s constructor, Carol\'s constructor')(format='.')
|
||
:marked
|
||
:marked
|
||
It's identical to *Carol*'s constructor except for the additional `@SkipSelf` decorator.
|
||
|
||
除了添加了一个额外的`@SkipSelf`外,它和*Carol*的构造函数一样。
|
||
|
||
`@SkipSelf` is essential for two reasons:
|
||
|
||
使用`@SkipSelf`主要是为了下面两个原因:
|
||
|
||
1. It tell the injector to start its search for a `Parent` dependency in a component *above* itself,
|
||
which *is* what parent means.
|
||
|
||
1. 它告诉注入器从一个在自己*上面*的组件开始搜索一个`Parent`依赖。
|
||
|
||
2. Angular throws a cyclic dependency error if we omit the `@SkipSelf` decorator.
|
||
|
||
2. 如果我们缺少`@SkipSelf`装饰器的话,Angular会抛出一个循环依赖错误。
|
||
|
||
`Cannot instantiate cyclic dependency! (BethComponent -> Parent -> BethComponent)`
|
||
|
||
`不能创建循环依赖实例!(BethComponent -> Parent -> BethComponent)`
|
||
|
||
Here's *Alice*, *Barry* and family in action:
|
||
|
||
这里是*Alice*,*Barry*和该家庭的实际代码:
|
||
|
||
figure.image-display
|
||
img(src="/resources/images/cookbooks/dependency-injection/alice.png" alt="Alice in action")
|
||
|
||
a(id="parent-token")
|
||
:marked
|
||
### The *Parent* class-interface
|
||
### *父级*类-接口
|
||
We [learned earlier](#class-interface) that a *class-interface* is an abstract class used as an interface rather than as a base class.
|
||
|
||
我们[之前学到](#class-interface):一个*类-接口*是一个抽象类,被当做一个接口来使用,而非被当做基本类使用。
|
||
|
||
Our example defines a `Parent` *class-interface* .
|
||
|
||
我们的例子定义了一个`Parent`*类-接口*。
|
||
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','parent','parent-finder.component.ts (Parent class-interface)')(format='.')
|
||
:marked
|
||
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 narrowing 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`的做法一样:
|
||
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','alice-class-signature','parent-finder.component.ts (AliceComponent class signature)')(format='.')
|
||
:marked
|
||
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`属性(被他的`基本`类要求),它的类签名并不提及`Parent`。
|
||
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','alex-class-signature','parent-finder.component.ts (AlexComponent class signature)')(format='.')
|
||
.l-sub-section
|
||
:marked
|
||
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(id="provideparent")
|
||
:marked
|
||
### A *provideParent* helper function
|
||
### 一个*privdeParent*帮助函数
|
||
|
||
Writing variations of the same parent *alias provider* gets old quickly,
|
||
especially this awful mouthful with a [*forwardRef*](#forwardref):
|
||
|
||
编写同一个父级的各种*别名provider*很快就变得很啰嗦,在用[*forwardRef](#forwardRef)的时候尤其绕口:
|
||
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','alex-providers')(format='.')
|
||
:marked
|
||
We can extract that logic into a helper function like this:
|
||
|
||
我们可以像这样把这个逻辑提取到一个帮助函数里面:
|
||
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','provide-the-parent')(format='.')
|
||
:marked
|
||
Now we can add a simpler, more meaningful parent provider to our components:
|
||
|
||
现在我们就可以为我们的组件添加一个简单点的,更加直观的父级provider:
|
||
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','alice-providers')(format='.')
|
||
:marked
|
||
We can do better. The current version of the helper function can only alias the `Parent` *class-interface*.
|
||
Our application might have a variety of parent types, each with its own *class-interface* token.
|
||
|
||
我们可以做的更好。当前版本的帮助函数只能为`父级`*类-接口*提供别名。我们的应用程序可能有很多类型的父级,每个父级有自己的*类-接口*令牌。
|
||
|
||
Here's a revised version that defaults to `parent` but also accepts an optional second parameter for a different parent *class-interface*.
|
||
|
||
这里是一个修改版本,默认接受一个`父级`,但是同时接受一个可选的第二个参数,可以用来指定一个不同的父级*类-接口*。
|
||
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','provide-parent')(format='.')
|
||
:marked
|
||
And here's how we could use it with a different parent type:
|
||
|
||
下面是我们如何使用它添加一个不同类型的父级:
|
||
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','beth-providers')(format='.')
|
||
:marked
|
||
|
||
a(id="forwardref")
|
||
.l-main-section
|
||
:marked
|
||
## Break circularities with a forward class reference (*forwardRef*)
|
||
## 使用一个转寄类引用(*forwardRef*)来来打破循环
|
||
|
||
The order of class declaration matters in TypeScript.
|
||
We can't refer directly to a class until it's been defined.
|
||
|
||
在TypeScript里面,类声明的顺序是有重要关系的。在一个类没有被定义之前,我们不能引用它。
|
||
|
||
This isn't usually a problem, especially if we adhere to the recommended *one class per file* rule.
|
||
But sometimes circular references are unavoidable.
|
||
We'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.
|
||
|
||
这个*父级寻找器*例子是到处都是类循环引用,我们无法打破。
|
||
|
||
In the [*Alex/Cathy* example](#known-parent) above:
|
||
|
||
在上面的[*Alex/Cathy*例子](#known-parent)中:
|
||
|
||
* the `AlexComponent` lists the `CathyComponent` in its component metadata `directives` array
|
||
so it can display *Cathy* in its template.
|
||
|
||
* `AlexComponent`在它的组件元数据`指令`数值里面列出`CathyComponent`,这样它可以在自己的模板中显示*Cathy*。
|
||
|
||
* the `CathyComponent` constructor injects the parent `AlexComponent` which means that the `alex` parameter
|
||
of its constructor has the `AlexComponent` type.
|
||
|
||
* `CathyComponent`的构造函数注入父级`AlexComponent`,这样的话,构造函数参数`alex`是`AlexComponent`类型。
|
||
|
||
*Alex* refers to *Cathy* and *Cathy* refers to *Alex*. We're stuck. We must define one of them first.
|
||
|
||
*Alex* 引用了*Cathy*,同时,*Cathy*引用了*Alex*。我们被卡住了。我们必须要先它们中的一个。
|
||
|
||
We defined *Alex* first and built its `C_DIRECTIVES` array with a forward reference to *Cathy*:
|
||
|
||
我们先定义了*Alex*,使用一个*Cathy*的转寄引用,建筑了它的`C_DIRECTIVES`数值:
|
||
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','C_DIRECTIVES','parent-finder.component.ts (C_DIRECTIVES)')(format='.')
|
||
:marked
|
||
.l-sub-section
|
||
:marked
|
||
Defining *Alex* and *Cathy* in separate files won't help.
|
||
*Alex* would have to import *Cathy* and *Cathy* would have to import *Alex*.
|
||
|
||
在单独的文件里面定义*Alex*和*Cathy*并不能帮忙。*Alex*必须要导入*Cathy*,*Cathy*必须要导入*Alex*。
|
||
We *had* to define *Alex* first because,
|
||
while we can add `forwardRef(CathyComponent)` to *Alex*'s `directives` array,
|
||
we can't write `public alex: forwardRef(AlexComponent))` in *Cathy*'s constructor.
|
||
|
||
我们*被迫*先定义*Alex*,因为我们可以添加`forwardRef(CathyComponent)`到*Alex*的`指令`数组里面,但是我们不能在*Cathy*的构造函数里面使用`public alex: forwardRef(AlexComponent))`。
|
||
:marked
|
||
We face a similar 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.
|
||
|
||
当一个类使用*一个自己的引用*的时候,我们面临同样的窘境,就像`AlexComponent`的`provdiers`数组里的困境一样。
|
||
该`providers`数组是一个`@Component`装饰器函数的一个属性,它必须要在类定义*之前*出现。
|
||
|
||
Again we break the circularity with `forwardRef`:
|
||
|
||
我们又一次使用`forwardRef`来打破该循环:
|
||
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','alex-providers','parent-finder.component.ts (AlexComponent providers)')(format='.')
|
||
:marked
|