The serve-and-sync tasks wait for `_copy-example-boilerplate` to finish Differentiates app compile, spec compile, and test failures StyleGuide (for documentators) up-to-date
		
			
				
	
	
		
			334 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			334 lines
		
	
	
		
			13 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.
 | |
| 
 | |
| <a id="toc"></a>
 | |
| :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)
 | |
| 
 | |
| :marked
 | |
|   **See the [live example](/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
 | |
|   
 | |
|   `HeroChildComponent` has two ***input properties***, 
 | |
|   typically adorned with [@Input decorations](../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'`.
 | |
|   
 | |
|   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/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
 | |
|   [Back to top](#top)
 | |
| 
 | |
| .l-main-section
 | |
| <a id="parent-to-child-setter"></a>
 | |
| :marked
 | |
|   ## 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. 
 | |
|   
 | |
| +makeExample('cb-component-communication/ts/app/name-child.component.ts')
 | |
| 
 | |
| :marked
 | |
|   Here's the `NameParentComponent` demonstrating name variations including a name with all spaces:
 | |
| 
 | |
| +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:
 | |
|   
 | |
| +makeExample('cb-component-communication/e2e-spec.ts', 'parent-to-child-setter')
 | |
| 
 | |
| :marked
 | |
|   [Back to top](#top)
 | |
| 
 | |
| .l-main-section
 | |
| <a id="parent-to-child-on-changes"></a>
 | |
| :marked
 | |
|   ## 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.
 | |
| .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.
 | |
| :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/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/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:
 | |
|   
 | |
| +makeExample('cb-component-communication/e2e-spec.ts', 'parent-to-child-onchanges')
 | |
| 
 | |
| :marked
 | |
|   [Back to 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.
 | |
|   
 | |
|   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/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.
 | |
|   
 | |
| +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:
 | |
|   
 | |
| 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
 | |
|   [Back to 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.
 | |
| +makeExample('cb-component-communication/ts/app/countdown-timer.component.ts')
 | |
| :marked
 | |
|   Let's see the `CountdownLocalVarParentComponent` that hosts the timer component.
 | |
|   
 | |
| +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.
 | |
|   
 | |
|   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.
 | |
|  
 | |
|   In this example, we wire parent buttons to the child's `start` and `stop` and
 | |
|   use interpolation to display the child's `seconds` property.
 | |
|   
 | |
|   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.ts', 'countdown-timer-tests')
 | |
| 
 | |
| :marked
 | |
|   [Back to top](#top)
 | |
|   
 | |
| .l-main-section
 | |
| <a id="parent-to-view-child"></a>
 | |
| :marked
 | |
|   ## Parent calls a *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*.
 | |
|   
 | |
|   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.
 | |
| .l-sub-section
 | |
|   :marked
 | |
|     We are switching from the *local variable* to the *ViewChild* technique
 | |
|     solely for the purpose of demonstration.
 | |
| :marked
 | |
|   Here is the parent, `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.
 | |
|   
 | |
|   We 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
 | |
|   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 timer component isn't available until *after* Angular displays the parent view.
 | |
|   So we display `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 
 | |
|   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
 | |
| <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.
 | |
| 
 | |
| +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:
 | |
|   
 | |
| +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:
 | |
|   
 | |
| +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.
 | |
|     
 | |
|     We do not 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:
 | |
|   
 | |
| +makeExample('cb-component-communication/e2e-spec.ts', 'bidirectional-service')
 | |
| 
 | |
| :marked
 | |
|   [Back to top](#top)
 |