diff --git a/public/docs/ts/latest/cookbook/component-communication.jade b/public/docs/ts/latest/cookbook/component-communication.jade index b2383bb22e..4a5e832386 100644 --- a/public/docs/ts/latest/cookbook/component-communication.jade +++ b/public/docs/ts/latest/cookbook/component-communication.jade @@ -13,21 +13,14 @@ include ../_util-fns :marked - ## Table of contents - - [Pass data from parent to child with input binding](#parent-to-child) + # Contents - [Intercept input property changes with a setter](#parent-to-child-setter) + - [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 calls an `@ViewChild()`](#parent-to-view-child) + - [Parent and children communicate via a service](#bidirectional-service) - [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) :marked **See the **. @@ -36,30 +29,30 @@ include ../_util-fns :marked ## Pass data from parent to child with input binding - - `HeroChildComponent` has two ***input properties***, + + `HeroChildComponent` has two ***input properties***, typically adorned with [@Input decorations](../guide/template-syntax.html#inputs-outputs). - + +makeExample('cb-component-communication/ts/src/app/hero-child.component.ts') :marked The second `@Input` aliases the child component property name `masterName` as `'master'`. - - The `HeroParentComponent` nests the child `HeroChildComponent` inside an `*ngFor` repeater, - binding its `master` string property to the child's `master` alias + + 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. +makeExample('cb-component-communication/ts/src/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.ts', 'parent-to-child') :marked @@ -71,10 +64,10 @@ figure.image-display ## Intercept input property changes with a setter Use an input property setter to intercept and act upon a value from the parent. - - 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. - + + 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. + +makeExample('cb-component-communication/ts/src/app/name-child.component.ts') :marked @@ -84,12 +77,12 @@ figure.image-display 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: - + +makeExample('cb-component-communication/e2e-spec.ts', 'parent-to-child-setter') :marked @@ -98,36 +91,36 @@ figure.image-display .l-main-section :marked - ## Intercept input property changes with *ngOnChanges* + ## Intercept input property changes with *ngOnChanges()* - Detect and act upon changes to input property values with the `ngOnChanges` method of the `OnChanges` lifecycle hook interface. + Detect and act upon changes to input property values with the `ngOnChanges()` method of the `OnChanges` lifecycle hook interface. .l-sub-section :marked - May prefer this approach to the property setter when watching multiple, interacting input properties. - - Learn about `ngOnChanges` in the [LifeCycle Hooks](../guide/lifecycle-hooks.html) chapter. + You may prefer this approach to the property setter when watching multiple, interacting input properties. + + Learn about `ngOnChanges()` in the [LifeCycle Hooks](../guide/lifecycle-hooks.html) chapter. :marked This `VersionChildComponent` detects changes to the `major` and `minor` input properties and composes a log message reporting these changes: - + +makeExample('cb-component-communication/ts/src/app/version-child.component.ts') :marked The `VersionParentComponent` supplies the `minor` and `major` values and binds buttons to methods that change them. - + +makeExample('cb-component-communication/ts/src/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 + + Test that ***both*** input properties are set initially and that button clicks trigger the expected `ngOnChanges` calls and values: - + +makeExample('cb-component-communication/e2e-spec.ts', 'parent-to-child-onchanges') :marked @@ -138,35 +131,35 @@ figure.image-display :marked ## Parent listens for child event - The child component exposes an `EventEmitter` property with which it `emits` events when something happens. + 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. - - The child's `EventEmitter` property is an ***output property***, + + 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`: - + +makeExample('cb-component-communication/ts/src/app/voter.component.ts') :marked - Clicking a button triggers emission of a `true` or `false` (the boolean *payload*). - - The parent `VoteTakerComponent` binds an event handler (`onVoted`) that responds to the child event - payload (`$event`) and updates a counter. - + Clicking a button triggers emission of a `true` or `false`, the boolean *payload*. + + The parent `VoteTakerComponent` binds an event handler called `onVoted()` that responds to the child event + payload `$event` and updates a counter. + +makeExample('cb-component-communication/ts/src/app/votetaker.component.ts') :marked - The framework passes the event argument — represented by `$event` — to the handler method, + The framework passes the event argument—represented by `$event`—to the handler method, and the method processes it: - + 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: - + +makeExample('cb-component-communication/e2e-spec.ts', 'child-to-parent') :marked @@ -175,33 +168,33 @@ figure.image-display .l-main-section#parent-to-child-local-var :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 + or invoke child methods. You 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. + The following is 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. +makeExample('cb-component-communication/ts/src/app/countdown-timer.component.ts') :marked - Let's see the `CountdownLocalVarParentComponent` that hosts the timer component. - + The `CountdownLocalVarParentComponent` that hosts the timer component is as follows: + +makeExample('cb-component-communication/ts/src/app/countdown-parent.component.ts', 'lv') :marked - The parent component cannot data bind to the child's + The parent component cannot data bind to the child's `start` and `stop` methods nor to its `seconds` property. - - 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 + + You can place a local variable, `#timer`, on the tag `` representing the child component. + That gives you a reference to the child component and the ability to access *any of its properties or methods* from within the parent template. - - In this example, we wire parent buttons to the child's `start` and `stop` and - use interpolation to display the child's `seconds` property. - + + This example wires parent buttons to the child's `start` and `stop` and + uses interpolation to display the child's `seconds` property. + Here we see the parent and child working together. figure.image-display @@ -210,72 +203,73 @@ figure.image-display 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.ts', 'countdown-timer-tests') :marked [Back to top](#top) - + .l-main-section :marked - ## Parent calls a *ViewChild* - - The *local variable* approach is simple and easy. But it is limited because + ## Parent calls an _@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* + + You 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*. - - We'll illustrate this technique with the same [Countdown Timer](#countdown-timer-example) example. - We won't change its appearance or behavior. + + When the parent component *class* requires that kind of access, + ***inject*** the child component into the parent as a *ViewChild*. + + The following example illustrates this technique with the same + [Countdown Timer](#countdown-timer-example) example. + Neither its appearance nor its behavior will change. The child [CountdownTimerComponent](#countdown-timer-example) is the same as well. .l-sub-section :marked - We are switching from the *local variable* to the *ViewChild* technique - solely for the purpose of demonstration. + The switch from the *local variable* to the *ViewChild* technique + is solely for the purpose of demonstration. :marked Here is the parent, `CountdownViewChildParentComponent`: +makeExample('cb-component-communication/ts/src/app/countdown-parent.component.ts', 'vc') :marked It takes a bit more work to get the child view into the parent component *class*. - - We import references to the `ViewChild` decorator and the `AfterViewInit` lifecycle hook. - - We inject the child `CountdownTimerComponent` into the private `timerComponent` property + + First, you have to import references to the `ViewChild` decorator and the `AfterViewInit` lifecycle hook. + + Next, inject the child `CountdownTimerComponent` into the private `timerComponent` property via the `@ViewChild` property decoration. - - 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 + + The `#timer` local variable is gone from the component metadata. + Instead, 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. - + These methods access the injected timer component directly. - - The `ngAfterViewInit` lifecycle hook is an important wrinkle. + + 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. - + So it displays `0` seconds initially. + 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. - - We use `setTimeout` to wait one tick and then revise the `seconds` method so + Angular's unidirectional data flow rule prevents updating the parent view's + in the same cycle. The app has to *wait one turn* before it can display the seconds. + + Use `setTimeout()` to wait one tick and then revise the `seconds()` method so that it takes future values from the timer component. ### Test it Use [the same countdown timer tests](#countdown-tests) as before. :marked [Back to top](#top) - + .l-main-section :marked @@ -284,47 +278,47 @@ a(id="countdown-tests") 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. + 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. +makeExample('cb-component-communication/ts/src/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: - + +makeExample('cb-component-communication/ts/src/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: - + +makeExample('cb-component-communication/ts/src/app/astronaut.component.ts') .l-sub-section :marked - Notice that we capture the `subscription` and unsubscribe when the `AstronautComponent` is destroyed. + Notice that this example captures 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. - - We do not add this guard to the `MissionControlComponent` because, as the parent, + + You don't add this guard to the `MissionControlComponent` because, as the parent, it controls the lifetime of the `MissionService`. :marked The *History* log demonstrates that messages travel in both directions between the parent `MissionControlComponent` and the `AstronautComponent` children, facilitated by the service: - + 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: - + and verify that the history meets expectations: + +makeExample('cb-component-communication/e2e-spec.ts', 'bidirectional-service') :marked