include ../_util-fns :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]()文档中可以找到详细的描述和例子。 :marked ## Table of contents ## 目录 [Pass data from parent to child with input binding](#parent-to-child) [Intercept input property changes with a setter](#parent-to-child-setter) [Intercept input property changes with *ngOnChanges*](#parent-to-child-on-changes) [Parent listens for child event](#child-to-parent) [Parent interacts with child via a *local variable*](#parent-to-child-local-var) [Parent calls a *ViewChild*](#parent-to-view-child) [Parent and children communicate via a service](#bidirectional-service) [使用输入Input绑定,从父级传数据到子级Pass data from parent to child with input binding](#parent-to-child) [通过setter截听Input属性值的变化Intercept input property changes with a setter](#parent-to-child-setter) [使用*ngOnChanges*截听Input属性值的变化Intercept input property changes with *ngOnChanges*](#parent-to-child-on-changes) [父级监听子级事件Parent listens for child event](#child-to-parent) [父级与子级通过*本地变量local variable*互动Parent interacts with child via a *local variable*](#parent-to-child-local-var) [父级调用*ViewChild*Parent calls a *ViewChild*](#parent-to-view-child) [父级和子级通过服务来交互Parent and children communicate via a 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 :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 :marked ## Intercept input property changes with a setter ## 通过setter截听Input属性值的变化Intercept 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`的`name` Input属性的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 :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`监测`major`和`minor`Input属性的变化,并根据这些变化编写一个日志信息报告。 +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 :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 :marked ## Parent interacts with child via *local variable* ## 父级与子级通过本地变量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. 一个父级组件不能使用数据绑定来读取子级属性或者调用子级类方法。我们可以在父级模板里,新建一个本地变量来代表子组件,然后实用这个变量来读取子级属性和调用子级类方法,如下例所示。 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 (``) 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`)放到(``)标签中,用来代表子级组件。这样我们就在父级组件获取子级组件的引用载体,在父级组件内访问子级的所有属性和方法。 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 :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 :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)