450 lines
21 KiB
Plaintext
450 lines
21 KiB
Plaintext
include ../_util-fns
|
||
|
||
<a id="top"></a>
|
||
:marked
|
||
This cookbook contains recipes for common component communication scenarios
|
||
in which two or more components share information.
|
||
|
||
本食谱包含了常见的组件之间的交互方法,也就是两个或多个组件之间共享信息的方法。
|
||
//
|
||
.l-sub-section
|
||
:marked
|
||
For an in-depth look at each fundamental concepts in component communication, we can find detailed description and
|
||
samples in the [Component Communication]() document.
|
||
|
||
要深度的了解组件交互的每个基本概念在,我们可以在[组件交互Component Communication]()文档中可以找到详细的描述和例子。
|
||
|
||
<a id="toc"></a>
|
||
:marked
|
||
## Table of contents
|
||
## 目录
|
||
|
||
[Pass data from parent to child with input binding](#parent-to-child)
|
||
|
||
[使用输入Input绑定,从父级传数据到子级](#parent-to-child)
|
||
|
||
[Intercept input property changes with a setter](#parent-to-child-setter)
|
||
|
||
[通过setter截听Input属性值的变化](#parent-to-child-setter)
|
||
|
||
[Intercept input property changes with *ngOnChanges*](#parent-to-child-on-changes)
|
||
|
||
[使用*ngOnChanges*截听Input属性值的变化](#parent-to-child-on-changes)
|
||
|
||
[Parent listens for child event](#child-to-parent)
|
||
|
||
[父级监听子级事件](#child-to-parent)
|
||
|
||
[Parent interacts with child via a *local variable*](#parent-to-child-local-var)
|
||
|
||
[父级与子级通过*本地变量local variable*互动](#parent-to-child-local-var)
|
||
|
||
[Parent calls a *ViewChild*](#parent-to-view-child)
|
||
|
||
[父级调用*ViewChild*](#parent-to-view-child)
|
||
|
||
[Parent and children communicate via a service](#bidirectional-service)
|
||
|
||
[父级和子级通过服务来交互](#bidirectional-service)
|
||
|
||
:marked
|
||
**See the [live example](/resources/live-examples/cb-component-communication/ts/plnkr.html)**.
|
||
|
||
**参见[在线例子](/resources/live-examples/cb-component-communication/ts/plnkr.html)**
|
||
|
||
.l-main-section
|
||
<a id="parent-to-child"></a>
|
||
:marked
|
||
## Pass data from parent to child with input binding
|
||
## 通过Input绑定从父级传送数据给子级。
|
||
|
||
`HeroChildComponent` has two ***input properties***, typically adorned with [@Input decorations](../guide/template-syntax.html#inputs-outputs).
|
||
|
||
`HeroChildComponent` 有两个***Input属性***, 通常用[@Input声明](../guide/template-syntax.html#inputs-outputs)来装饰。
|
||
|
||
+makeExample('cb-component-communication/ts/app/hero-child.component.ts')
|
||
:marked
|
||
The second `@Input` aliases the child component property name `masterName` as `'master'`.
|
||
|
||
第二个`@Input`是为子组件的属性名`masterName`起别名为`master`(译者注:不推荐为起别名,请参见风格指南).
|
||
|
||
The `HeroParentComponent` nests the child `HeroChildComponent` inside an `*ngFor` repeater, binding its `master` string property to the child's `master` alias and each iteration's `hero` instance to the child's `hero` property.
|
||
|
||
父级`HeroParentComponent`把子级 `HeroChildComponent`放到`*ngFor`循环器中,把自己的`master`字符串属性绑定到子级的`master`别名上,并把每个循环的`hero`实例绑定到子级的`hero`属性。
|
||
|
||
+makeExample('cb-component-communication/ts/app/hero-parent.component.ts')
|
||
:marked
|
||
The running application displays three heroes:
|
||
|
||
运行的应用程序显示三个英雄:
|
||
|
||
figure.image-display
|
||
img(src="/resources/images/cookbooks/component-communication/parent-to-child.png" alt="Parent-to-child")
|
||
|
||
:marked
|
||
### Test it
|
||
### 测试
|
||
|
||
E2E test that all children were instantiated and displayed as expected:
|
||
|
||
实施端对端测试,以确认所有子级的初始化和显示与我们预期的一样。
|
||
|
||
+makeExample('cb-component-communication/e2e-spec.js', 'parent-to-child')
|
||
|
||
:marked
|
||
[Back to top](#top)
|
||
|
||
[返回到顶部](#top)
|
||
|
||
.l-main-section
|
||
<a id="parent-to-child-setter"></a>
|
||
:marked
|
||
## Intercept input property changes with a setter
|
||
|
||
## 通过setter截听Input属性值的变化
|
||
|
||
Use an input property setter to intercept and act upon a value from the parent.
|
||
|
||
使用一个Input属性setter,在父级拦截子级属性,并针对属性值的变化采取行动。
|
||
|
||
The setter of the `name` input property in the child `NameChildComponent` trims the whitespace from a name and replaces an empty value with default text.
|
||
|
||
这个在子组件`NameChildComponent`的Input属性`name`的setter,用来修剪名字里面的空格,并替换空值为默认字符串。
|
||
|
||
+makeExample('cb-component-communication/ts/app/name-child.component.ts')
|
||
|
||
:marked
|
||
Here's the `NameParentComponent` demonstrating name variations including a name with all spaces:
|
||
|
||
下面的`NameParentComponent`展示了各种名字的处理,包括一个全是空格的名字。
|
||
|
||
+makeExample('cb-component-communication/ts/app/name-parent.component.ts')
|
||
|
||
figure.image-display
|
||
img(src="/resources/images/cookbooks/component-communication/setter.png" alt="Parent-to-child-setter")
|
||
|
||
:marked
|
||
### Test it
|
||
|
||
E2E tests of input property setter with empty and non-empty names:
|
||
|
||
端对端测试Input属性的setter,使用空名字和非空名字。
|
||
|
||
+makeExample('cb-component-communication/e2e-spec.js', 'parent-to-child-setter')
|
||
|
||
:marked
|
||
[Back to top](#top)
|
||
|
||
[回到顶部](#top)
|
||
|
||
.l-main-section
|
||
<a id="parent-to-child-on-changes"></a>
|
||
:marked
|
||
## Intercept input property changes with *ngOnChanges*
|
||
|
||
## 通过*ngOnChanges*来截听Input属性值的变化
|
||
|
||
Detect and act upon changes to input property values with the `ngOnChanges` method of the `OnChanges` lifecycle hook interface.
|
||
|
||
使用`ngOnChanges`函数方法和`OnChanges`生命周期接口来监测Input属性值的变化并做出相应回应。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
May prefer this approach to the property setter when watching multiple, interacting input properties.
|
||
|
||
当监视多个互动Input属性的时候,本方法比属性setter更合适。
|
||
|
||
Learn about `ngOnChanges` in the [LifeCycle Hooks](../guide/lifecycle-hooks.html) chapter.
|
||
|
||
到[生命周期钩子](../guide/lifecycle-hooks.html)章节学习更多关于`ngOnChanges`的知识。
|
||
|
||
:marked
|
||
This `VersionChildComponent` detects changes to the `major` and `minor` input properties and composes a log message reporting these changes:
|
||
|
||
这个`VersionChildComponent`监测Input属性`major`和`minor`的变化,并根据这些变化编写一个日志报告。
|
||
|
||
+makeExample('cb-component-communication/ts/app/version-child.component.ts')
|
||
|
||
:marked
|
||
The `VersionParentComponent` supplies the `minor` and `major` values and binds buttons to methods that change them.
|
||
|
||
`VersionParentComponent`提供`minor`和`major`值,将变换它们值的函数方法绑定到按钮上。
|
||
|
||
+makeExample('cb-component-communication/ts/app/version-parent.component.ts')
|
||
|
||
:marked
|
||
Here's the output of a button-pushing sequence:
|
||
|
||
下面是点击按钮的结果。
|
||
|
||
figure.image-display
|
||
img(src="/resources/images/cookbooks/component-communication/parent-to-child-on-changes.gif" alt="Parent-to-child-onchanges")
|
||
|
||
:marked
|
||
### Test it
|
||
### 测试
|
||
|
||
Test that ***both*** input properties are set initially and that button clicks trigger the expected `ngOnChanges` calls and values:
|
||
|
||
测试确认***两个***Input属性值都被初始化了,并在点击按钮后,`ngOnChanges`被调用,属性值的按预期变化。
|
||
|
||
+makeExample('cb-component-communication/e2e-spec.js', 'parent-to-child-onchanges')
|
||
|
||
:marked
|
||
[Back to top](#top)
|
||
[回到顶部](#top)
|
||
|
||
.l-main-section
|
||
<a id="child-to-parent"></a>
|
||
:marked
|
||
## Parent listens for child event
|
||
## 父级监听子级事件
|
||
|
||
The child component exposes an `EventEmitter` property with which it `emits`events when something happens. The parent binds to that event property and reacts to those events.
|
||
|
||
子级组件暴露一个`EventEmitter`属性,当事情发生时,子级组件利用该属性`散发`事件。父级绑定这个事件属性,并在事件发生时作出回应。
|
||
|
||
The child's `EventEmitter` property is an ***output property***, typically adorned with an [@Output decoration](../guide/template-syntax.html#inputs-outputs) as seen in this `VoterComponent`:
|
||
|
||
子级组件的`EventEmitter`属性是一个**Output属性**,通常用[@Output装饰器](../guide/template-syntax.html#inputs-outputs)来装饰,正如在`VoterComponent`中所见。
|
||
|
||
+makeExample('cb-component-communication/ts/app/voter.component.ts')
|
||
|
||
:marked
|
||
Clicking a button triggers emission of a `true` or `false` (the boolean *payload*).
|
||
|
||
点击按钮触发`true`或`false`的事件(布尔酬载)
|
||
|
||
The parent `VoteTakerComponent` binds an event handler (`onVoted`) that responds to the child event payload (`$event`) and updates a counter.
|
||
|
||
父级`VoteTakerComponent`绑定一个事件处理器(`onVoted`),用来回应子组件时间事件(`$event`)并更新一个计数器。
|
||
|
||
+makeExample('cb-component-communication/ts/app/votetaker.component.ts')
|
||
|
||
:marked
|
||
The framework passes the event argument — represented by `$event` — to the handler method, and the method processes it:
|
||
|
||
框架(Angular)传递利用`$event`把事件参数传给事件处理函数方法,由这个函数方法来处理:
|
||
|
||
figure.image-display
|
||
img(src="/resources/images/cookbooks/component-communication/child-to-parent.gif" alt="Child-to-parent")
|
||
|
||
:marked
|
||
### Test it
|
||
### 测试
|
||
|
||
Test that clicking the *Agree* and *Disagree* buttons update the appropriate counters:
|
||
|
||
测试验证点击*Agree*和*Disagree*按钮时,计数器更新是否正确。
|
||
|
||
+makeExample('cb-component-communication/e2e-spec.js', 'child-to-parent')
|
||
|
||
:marked
|
||
[Back to top](#top)
|
||
|
||
[回到顶部](#top)
|
||
|
||
parent-to-child-local-var
|
||
.l-main-section
|
||
<a id="parent-to-child-local-var"></a>
|
||
:marked
|
||
## Parent interacts with child via *local variable*
|
||
## 父级与子级通过*本地变量*互动
|
||
|
||
A parent component cannot use data binding to read child properties or invoke child methods. We can do both by creating a template reference variable for the child element and then reference that variable *within the parent template* as seen in the following example.
|
||
|
||
父级组件不能使用数据绑定来读取子级属性或者调用子级函数方法。我们可以在父级模板里,新建一个本地变量来代表子组件,然后利用这个变量来读取子级属性和调用子级函数方法,如下例所示。
|
||
|
||
<a id="countdown-timer-example"></a>
|
||
We have a child `CountdownTimerComponent` that repeatedly counts down to zero and launches a rocket. It has `start` and `stop` methods that control the clock and it displays a countdown status message in its own template.
|
||
|
||
子级组件`CountdownTimerComponent`反复的倒计时,到零时发射一个导弹。`start`和`stop`函数方法控制时间和在自己的模板里面显示倒计时状态信息。
|
||
|
||
+makeExample('cb-component-communication/ts/app/countdown-timer.component.ts')
|
||
:marked
|
||
Let's see the `CountdownLocalVarParentComponent` that hosts the timer component.
|
||
|
||
让我们来看看计时器组件的宿主组件`CountdownLocalVarParentComponent`。
|
||
|
||
+makeExample('cb-component-communication/ts/app/countdown-parent.component.ts', 'lv')
|
||
:marked
|
||
The parent component cannot data bind to the child's `start` and `stop` methods nor to its `seconds` property.
|
||
|
||
父级组件不能数据绑定到子级的`start`和`stop`方法,也不能绑定到子级的`seconds`属性。
|
||
|
||
We can place a local variable (`#timer`) on the tag (`<countdown-timer>`) representing the child component. That gives us a reference to the child component itself and the ability to access *any of its properties or methods* from within the parent template.
|
||
|
||
我们把本地变量(`#timer`)放到(`<countdown-timer>`)标签中,用来代表子级组件。这样父级组件的模板便获取了子级组件的引用载体,在父级组件模板内访问子级的所有属性和方法。
|
||
|
||
In this example, we wire parent buttons to the child's `start` and `stop` and use interpolation to display the child's `seconds` property.
|
||
|
||
在这个例子中,我们把父级组件的按钮绑定到子级的`start`和`stop`方法,并用插值表达式来显示子级的`seconds`属性。
|
||
|
||
Here we see the parent and child working together.
|
||
|
||
下面是父级和子级一起工作的结果。
|
||
|
||
figure.image-display
|
||
img(src="/resources/images/cookbooks/component-communication/countdown-timer-anim.gif" alt="countdown timer")
|
||
|
||
a(id="countdown-tests")
|
||
:marked
|
||
### Test it
|
||
### 测试
|
||
|
||
Test that the seconds displayed in the parent template match the seconds displayed in the child's status message. Test also that clicking the *Stop* button pauses the countdown timer:
|
||
|
||
测试确认在父级模板中显示的秒钟和子级组件里的状态信息里面的秒钟同步:
|
||
|
||
+makeExample('cb-component-communication/e2e-spec.js', 'countdown-timer-tests')
|
||
|
||
:marked
|
||
[Back to top](#top)
|
||
|
||
[回到顶部](#top)
|
||
|
||
.l-main-section
|
||
<a id="parent-to-view-child"></a>
|
||
:marked
|
||
## Parent calls a *ViewChild*
|
||
## 父级调用*ViewChild*
|
||
|
||
The *local variable* approach is simple and easy. But it is limited because the parent-child wiring must be done entirely within the parent template. The parent component *itself* has no access to the child.
|
||
|
||
上面*本地变量*的方法是一个简单和便利的方法。但是它有局限性,因为父级-子级的连接必须要全部都在父级的模板里面进行。父级组件本身对子级没有访问权。
|
||
|
||
We can't use the *local variable* technique if an instance of the parent component *class* must read or write child component values or must call child component methods.
|
||
|
||
如果父级组件的*类*需要读取子级组件的属性值或者调用子级组件的函数方法,我们就不能使用*本地变量*方法。
|
||
|
||
When the parent component *class* requires that kind of access, we ***inject*** the child component into the parent as a *ViewChild*.
|
||
|
||
当父级组件*类*需要这种访问时,我们把子级组件作为*ViewChild*,***注入***到父级组件里面。
|
||
|
||
We'll illustrate this technique with the same [Countdown Timer](#countdown-timer-example) example. We won't change its appearance or behavior. The child [CountdownTimerComponent](#countdown-timer-example) is the same as well.
|
||
|
||
我们将会用一样的[倒计时](#countdown-timer-example)例子来解释这个技巧。我们没有变化它的样子或行为。子级[CountdownTimerComponent](#countdown-timer-example)和原来也一样。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
We are switching from the *local variable* to the *ViewChild* technique solely for the purpose of demonstration.
|
||
|
||
我们由*本地变量*切换到*ViewChild*技巧的唯一目的是做一个示范。
|
||
|
||
:marked
|
||
Here is the parent, `CountdownViewChildParentComponent`:
|
||
|
||
下面是父级`CountdownViewChildParentComponent`:
|
||
|
||
+makeExample('cb-component-communication/ts/app/countdown-parent.component.ts', 'vc')
|
||
:marked
|
||
It takes a bit more work to get the child view into the parent component classs.
|
||
|
||
把子级视图插入到父级组件类需要一点额外的工作。
|
||
|
||
We import references to the `ViewChild` decorator and the `AfterViewInit` lifecycle hook.
|
||
|
||
我们需要通过`ViewChild`装饰器导入引用,并应用`AfterViewInit`生命周期钩子。
|
||
|
||
We inject the child `CountdownTimerComponent` into the private `_timerComponent` property via the `@ViewChild` property decoration.
|
||
|
||
我们通过`@ViewChild`属性装饰器,将子级`CountdownTimerComponent`注入到私有属性`_timerComponent`里面。
|
||
|
||
The `#timer` local variable is gone from the component metadata. Instead we bind the buttons to the parent component's own `start` and `stop` methods and present the ticking seconds in an interpolation around the parent component's `seconds` method.
|
||
|
||
组件元数据里便不需要`#timer`本地变量了。取而代之,我们把按钮绑定到父级组件自己的`start`和`stop`方法,使用父级组件的`seconds`方法的插值表达式来展示秒钟变化。
|
||
|
||
These methods access the injected timer component directly.
|
||
|
||
这些函数方法直接访问被注入的计时器组件。
|
||
|
||
The `ngAfterViewInit` lifecycle hook is an important wrinkle. The timer component isn't available until *after* Angular displays the parent view. So we display `0` seconds initially.
|
||
|
||
`ngAfterViewInit`生命周期钩子是一个非常重要的步骤。被注入的计时器组件只在Angular显示了父级视图之后才可以被访问,所以我们先显示秒钟为0.
|
||
|
||
Then Angular calls the `ngAfterViewInit` lifecycle hook at which time it is *too late* to update the parent view's display of the countdown seconds. Angular's unidirectional data flow rule prevents us from updating the parent view's in the same cycle. We have to *wait one turn* before we can display the seconds.
|
||
|
||
然后Angular调用`ngAfterViewInit`生命周期钩子,但是这时候再更新父级视图的倒计时就已经太晚了。Angular的单向数据流动规则防止我们在同一个周期内更新父级视图。我们在显示秒钟之前需要被迫*等待一轮*。
|
||
|
||
We use `setTimeout` to wait one tick and then revise the `seconds` method so that it takes future values from the timer component.
|
||
|
||
我们使用`setTimeout`来等待一轮,然后重组`seconds`方法,这样它接下来从注入的计时器组件里面获取秒钟的值。
|
||
|
||
### Test it
|
||
## 测试
|
||
|
||
Use [the same countdown timer tests](#countdown-tests) as before.
|
||
|
||
使用之前的[一样的倒计时测试](#countdown-tests)。
|
||
|
||
:marked
|
||
[Back to top](#top)
|
||
|
||
[回到顶部]('#top')
|
||
|
||
.l-main-section
|
||
<a id="bidirectional-service"></a>
|
||
:marked
|
||
## Parent and children communicate via a service
|
||
## 父级和子级通过服务来交互
|
||
|
||
A parent component and its children share a service whose interface enables bi-directional communication *within the family*.
|
||
|
||
父级组件和它的子级组件共享一个服务,利用该服务*在本家庭之内*实现双向交互。
|
||
|
||
The scope of the service instance is the parent component and its children. Components outside this component subtree have no access to the service or their communications.
|
||
|
||
该服务实例的作用范围被限制在父级组件和其子级组件内。在这个组件子树之外的组件无法访问该服务或者它们之间的交互。
|
||
|
||
This `MissionService` connects the `MissionControlComponent` to multiple `AstronautComponent` children.
|
||
|
||
这个`MissionService`连接`MissionControlComponent`和多个`AstronautComponent`子级。
|
||
|
||
+makeExample('cb-component-communication/ts/app/mission.service.ts')
|
||
:marked
|
||
The `MissionControlComponent` both provides the instance of the service that it shares with its children (through the `providers` metadata array) and injects that instance into itself through its constructor:
|
||
|
||
`MissionControlComponent`提供服务的实例,将其共享给它的子级(通过`providers`元数据数组),并通过自身的构造函数将该实例注入给自己。
|
||
|
||
+makeExample('cb-component-communication/ts/app/missioncontrol.component.ts')
|
||
|
||
:marked
|
||
The `AstronautComponent` also injects the service in its constructor. Each `AstronautComponent` is a child of the `MissionControlComponent` and therefore receives its parent's service instance:
|
||
|
||
`AstronautComponent`也通过自己的构造函数注入该服务。每个`AstronautComponent`都是`MissionControlComponent`的子级,所以它们获取它们父级的服务的实例。
|
||
|
||
+makeExample('cb-component-communication/ts/app/astronaut.component.ts')
|
||
|
||
.l-sub-section
|
||
:marked
|
||
Notice that we capture the `subscription` and unsubscribe when the `AstronautComponent` is destroyed. This is a memory-leak guard step. There is no actual risk in this app because the lifetime of a `AstronautComponent` is the same as the lifetime of the app itself. That *would not* always be true in a more complex application.
|
||
|
||
注意,我们通过`subscription`获取任务,并在`AstronautComponent`被销毁的时候退订。这是一个防止内存泄漏的保护措施。实际上,在这个应用程序中没有这个风险,因为`AstronautComponent`的生命期和应用程序的生命期一样。但是在更复杂的应用程序环境中就不一定了。
|
||
|
||
We do not add this guard to the `MissionControlComponent` because, as the parent, it controls the lifetime of the `MissionService`.
|
||
|
||
我们不需要在`MissionControlComponent`中添加这个保护措施,因为作为父级,它控制`MissionService`的生命期。
|
||
|
||
:marked
|
||
The *History* log demonstrates that messages travel in both directions between the parent `MissionControlComponent` and the `AstronautComponent` children, facilitated by the service:
|
||
|
||
*History*日志证明了在父级`MissionControlComponent`和子级`AstronautComponent`之间,信息通过该服务实现了双向传递。
|
||
|
||
figure.image-display
|
||
img(src="/resources/images/cookbooks/component-communication/bidirectional-service.gif" alt="bidirectional-service")
|
||
|
||
:marked
|
||
### Test it
|
||
### 测试
|
||
|
||
Tests click buttons of both the parent `MissionControlComponent` and the `AstronautComponent` children and verify that the *History* meets expectations:
|
||
|
||
测试检查点击父级`MissionControlComponent`和子级`AstronautComponent`两个的组件的按钮时,*History*日志和预期的一样。
|
||
|
||
+makeExample('cb-component-communication/e2e-spec.js', 'bidirectional-service')
|
||
|
||
:marked
|
||
[Back to top](#top)
|
||
|
||
[回到顶部](#top)
|