docs(comp comm cookbook): add #localvar alternative for parent calling child
also fixes glossary decorator-flag bug
This commit is contained in:
parent
b0f1f3a4b8
commit
513e919be5
|
@ -150,23 +150,38 @@ describe('Component Communication Cookbook Tests', function () {
|
|||
// #enddocregion child-to-parent
|
||||
});
|
||||
|
||||
describe('Parent calls child via local var', function() {
|
||||
countDownTimerTests('countdown-parent-lv')
|
||||
});
|
||||
|
||||
describe('Parent calls ViewChild', function() {
|
||||
// #docregion parent-to-view-child
|
||||
countDownTimerTests('countdown-parent-vc')
|
||||
});
|
||||
|
||||
function countDownTimerTests(parentTag) {
|
||||
// #docregion countdown-timer-tests
|
||||
// ...
|
||||
it('timer and parent seconds should match', function () {
|
||||
var parent = element(by.tagName(parentTag));
|
||||
var message = parent.element(by.tagName('countdown-timer')).getText();
|
||||
browser.sleep(10); // give `seconds` a chance to catchup with `message`
|
||||
var seconds = parent.element(by.className('seconds')).getText();
|
||||
expect(message).toContain(seconds);
|
||||
});
|
||||
|
||||
it('should stop the countdown', function () {
|
||||
var stopButton = element
|
||||
.all(by.tagName('countdown-parent')).get(0)
|
||||
.all(by.tagName('button')).get(1);
|
||||
var parent = element(by.tagName(parentTag));
|
||||
var stopButton = parent.all(by.tagName('button')).get(1);
|
||||
|
||||
stopButton.click().then(function() {
|
||||
var message = element(by.tagName('countdown-timer'))
|
||||
.element(by.tagName('p')).getText();
|
||||
var message = parent.element(by.tagName('countdown-timer')).getText();
|
||||
expect(message).toContain('Holding');
|
||||
});
|
||||
});
|
||||
// ...
|
||||
// #enddocregion parent-to-view-child
|
||||
});
|
||||
// #enddocregion countdown-timer-tests
|
||||
}
|
||||
|
||||
|
||||
describe('Parent and children communicate via a service', function() {
|
||||
// #docregion bidirectional-service
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<h1 id="top">Component Communication Cookbook</h1>
|
||||
|
||||
<a href="#parent-to-child">Pass data from parent to child with input binding</a><br/>
|
||||
<a href="#parent-to-child-setter">Intercept input property changes with a setter</a><br/>
|
||||
<a href="#parent-to-child-on-changes">Intercept input property changes with <i>ngOnChanges</i></a><br/>
|
||||
<a href="#child-to-parent">Parent listens for child event</a><br/>
|
||||
<a href="#parent-to-view-child">Parent calls <i>ViewChild</i></a><br/>
|
||||
<a href="#bidirectional-service">Parent and children communicate via a service</a><br/>
|
||||
<a href="#parent-to-child">Pass data from parent to child with input binding ("Heros")</a><br/>
|
||||
<a href="#parent-to-child-setter">Intercept input property changes with a setter ("Master")</a><br/>
|
||||
<a href="#parent-to-child-on-changes">Intercept input property changes with <i>ngOnChanges</i> ("Source code version")</a><br/>
|
||||
<a href="#child-to-parent">Parent listens for child event ("Colonize Universe")</a><br/>
|
||||
<a href="#parent-to-child-local-var">Parent to child via <i>local variable</i>("Countdown to Liftoff")</a><br/>
|
||||
<a href="#parent-to-view-child">Parent calls <i>ViewChild</i>("Countdown to Liftoff")</a><br/>
|
||||
<a href="#bidirectional-service">Parent and children communicate via a service ("Mission Control")</a><br/>
|
||||
|
||||
<div id="parent-to-child">
|
||||
<hero-parent></hero-parent>
|
||||
|
@ -31,8 +32,14 @@
|
|||
<a href="#top" class="to-top">Back to Top</a>
|
||||
<hr>
|
||||
|
||||
<div id="parent-to-child-local-var">
|
||||
<countdown-parent-lv></countdown-parent-lv>
|
||||
</div>
|
||||
<a href="#top" class="to-top">Back to Top</a>
|
||||
<hr>
|
||||
|
||||
<div id="parent-to-view-child">
|
||||
<countdown-parent></countdown-parent>
|
||||
<countdown-parent-vc></countdown-parent-vc>
|
||||
</div>
|
||||
<a href="#top" class="to-top">Back to Top</a>
|
||||
<hr>
|
||||
|
|
|
@ -3,7 +3,8 @@ import {HeroParentComponent} from './hero-parent.component';
|
|||
import {NameParentComponent} from './name-parent.component';
|
||||
import {VersionParentComponent} from './version-parent.component';
|
||||
import {VoteTakerComponent} from './votetaker.component';
|
||||
import {CountdownParentComponent} from './countdown-parent.component';
|
||||
import {CountdownLocalVarParentComponent,
|
||||
CountdownViewChildParentComponent} from './countdown-parent.component';
|
||||
import {MissionControlComponent} from './missioncontrol.component';
|
||||
|
||||
@Component({
|
||||
|
@ -14,7 +15,8 @@ import {MissionControlComponent} from './missioncontrol.component';
|
|||
NameParentComponent,
|
||||
VersionParentComponent,
|
||||
VoteTakerComponent,
|
||||
CountdownParentComponent,
|
||||
CountdownLocalVarParentComponent,
|
||||
CountdownViewChildParentComponent,
|
||||
MissionControlComponent
|
||||
]
|
||||
})
|
||||
|
|
|
@ -1,22 +1,59 @@
|
|||
// #docregion
|
||||
import {Component, ViewChild} from 'angular2/core';
|
||||
// #docplaster
|
||||
// #docregion vc
|
||||
import {AfterViewInit, ViewChild} from 'angular2/core';
|
||||
// #docregion lv
|
||||
import {Component} from 'angular2/core';
|
||||
import {CountdownTimerComponent} from './countdown-timer.component';
|
||||
|
||||
// #enddocregion lv
|
||||
// #enddocregion vc
|
||||
|
||||
//// Local variable, #timer, version
|
||||
// #docregion lv
|
||||
@Component({
|
||||
selector:'countdown-parent',
|
||||
selector:'countdown-parent-lv',
|
||||
template: `
|
||||
<h3>Countdown to Liftoff</h3>
|
||||
<h3>Countdown to Liftoff (via local variable)</h3>
|
||||
<button (click)="timer.start()">Start</button>
|
||||
<button (click)="timer.stop()">Stop</button>
|
||||
<div class="seconds">{{timer.seconds}}</div>
|
||||
<countdown-timer #timer></countdown-timer>
|
||||
`,
|
||||
directives: [CountdownTimerComponent],
|
||||
styleUrls: ['demo.css']
|
||||
})
|
||||
export class CountdownLocalVarParentComponent { }
|
||||
// #enddocregion lv
|
||||
|
||||
//// View Child version
|
||||
// #docregion vc
|
||||
@Component({
|
||||
selector:'countdown-parent-vc',
|
||||
template: `
|
||||
<h3>Countdown to Liftoff (via ViewChild)</h3>
|
||||
<button (click)="start()">Start</button>
|
||||
<button (click)="stop()">Stop</button>
|
||||
<div class="seconds">{{ seconds() }}</div>
|
||||
<countdown-timer></countdown-timer>
|
||||
`,
|
||||
directives: [CountdownTimerComponent]
|
||||
directives: [CountdownTimerComponent],
|
||||
styleUrls: ['demo.css']
|
||||
})
|
||||
export class CountdownParentComponent {
|
||||
export class CountdownViewChildParentComponent implements AfterViewInit {
|
||||
|
||||
@ViewChild(CountdownTimerComponent)
|
||||
private _timerComponent:CountdownTimerComponent;
|
||||
|
||||
seconds() { return 0; }
|
||||
|
||||
ngAfterViewInit() {
|
||||
// Redefine `seconds()` to get from the `CountdownTimerComponent.seconds` ...
|
||||
// but wait a tick first to avoid one-time devMode
|
||||
// unidirectional-data-flow-violation error
|
||||
setTimeout(() => this.seconds = () => this._timerComponent.seconds, 0)
|
||||
}
|
||||
|
||||
start(){ this._timerComponent.start(); }
|
||||
stop() { this._timerComponent.stop(); }
|
||||
}
|
||||
// #enddocregion vc
|
||||
|
|
|
@ -28,8 +28,8 @@ export class CountdownTimerComponent implements OnInit, OnDestroy {
|
|||
this.seconds -= 1;
|
||||
if (this.seconds == 0) {
|
||||
this.message = "Blast off!";
|
||||
this.seconds = 11; // reset
|
||||
} else {
|
||||
if (this.seconds < 0) { this.seconds = 10;} // reset
|
||||
this.message = `T-${this.seconds} seconds and counting`;
|
||||
}
|
||||
}, 1000);
|
||||
|
|
|
@ -11,7 +11,7 @@ import {MissionService} from './mission.service';
|
|||
<my-astronaut *ngFor="#astronaut of astronauts"
|
||||
[astronaut]="astronaut">
|
||||
</my-astronaut>
|
||||
<h2>History</h2>
|
||||
<h3>History</h3>
|
||||
<ul>
|
||||
<li *ngFor="#event of history">{{event}}</li>
|
||||
</ul>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
/* Component Communication cookbook specific styles */
|
||||
.seconds {
|
||||
background-color: black;
|
||||
color: red;
|
||||
font-size: 3em;
|
||||
margin: 0.3em 0;
|
||||
text-align: center;
|
||||
width: 1.5em;
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
.to-top {margin-top: 8px; display: block;}
|
||||
</style>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="demo.css">
|
||||
|
||||
<!-- IE required polyfills, in this exact order -->
|
||||
<script src="node_modules/es6-shim/es6-shim.min.js"></script>
|
||||
|
|
|
@ -23,6 +23,8 @@ include ../_util-fns
|
|||
|
||||
[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)
|
||||
|
@ -167,6 +169,56 @@ figure.image-display
|
|||
|
||||
+makeExample('cb-component-communication/e2e-spec.js', '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 local 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.js', 'countdown-timer-tests')
|
||||
|
||||
:marked
|
||||
[Back to top](#top)
|
||||
|
||||
|
@ -174,31 +226,55 @@ figure.image-display
|
|||
<a id="parent-to-view-child"></a>
|
||||
:marked
|
||||
## Parent calls a *ViewChild*
|
||||
A parent can call a child component once it has been located by a property adorned with a `@ViewChild` decorator property.
|
||||
|
||||
This `CountdownTimerComponent` keeps counting down to zero and launching rockets.
|
||||
It has `start` and `stop` methods that control the countdown.
|
||||
+makeExample('cb-component-communication/ts/app/countdown-timer.component.ts')
|
||||
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
|
||||
The parent `CountdownParentComponent` cannot bind to the child's `start` and `stop` methods.
|
||||
But it can obtain a reference to the child component by applying a `@ViewChild` decorator
|
||||
to a receiver property (`timerComponent`) after giving that decorator the type of component to find.
|
||||
Once it has that reference, it can access *any property or method* of the child component.
|
||||
|
||||
Here it wires its own buttons to the child's start` and `stop`.
|
||||
|
||||
+makeExample('cb-component-communication/ts/app/countdown-parent.component.ts')
|
||||
We are switching from the *local variable* to the *ViewChild* technique
|
||||
solely for the purpose of demonstration.
|
||||
:marked
|
||||
|
||||
figure.image-display
|
||||
img(src="/resources/images/cookbooks/component-communication/countdown-timer-anim.gif" alt="countdown timer")
|
||||
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
|
||||
|
||||
Test that clicking the *Stop* button pauses the countdown timer:
|
||||
|
||||
+makeExample('cb-component-communication/e2e-spec.js', 'parent-to-view-child')
|
||||
|
||||
Use [the same countdown timer tests](#countdown-tests) as before.
|
||||
:marked
|
||||
[Back to top](#top)
|
||||
|
||||
|
|
|
@ -42,8 +42,8 @@ include _util-fns
|
|||
|
||||
// #docregion b-c
|
||||
- var lang = current.path[1]
|
||||
- var decorator = lang = 'dart' ? 'annotation' : '[decorator](#decorator)'
|
||||
- var atSym = lang == 'js' ? '' : '@'
|
||||
- var decorator = lang === 'dart' ? 'annotation' : '<a href="#decorator">decorator</a>'
|
||||
- var atSym = lang === 'js' ? '' : '@'
|
||||
<a id="B"></a>
|
||||
.l-main-section
|
||||
:marked
|
||||
|
@ -116,7 +116,7 @@ include _util-fns
|
|||
The Component is one of the most important building blocks in the Angular system.
|
||||
It is, in fact, an Angular [Directive](#directive) with a companion [Template](#template).
|
||||
|
||||
The developer applies the `#{atSym}Component` #{decorator} to
|
||||
The developer applies the `#{atSym}Component` !{decorator} to
|
||||
the component class, thereby attaching to the class the essential component metadata
|
||||
that Angular needs to create a component instance and render it with its template
|
||||
as a view.
|
||||
|
@ -446,8 +446,8 @@ include _util-fns
|
|||
|
||||
// #docregion n-s
|
||||
- var lang = current.path[1]
|
||||
- var decorator = lang = 'dart' ? 'annotation' : '[decorator](#decorator)'
|
||||
- var atSym = lang == 'js' ? '' : '@'
|
||||
- var decorator = lang === 'dart' ? 'annotation' : '<a href="#decorator">decorator</a>'
|
||||
- var atSym = lang === 'js' ? '' : '@'
|
||||
<a id="N"></a>
|
||||
<a id="O"></a>
|
||||
.l-main-section
|
||||
|
@ -469,7 +469,7 @@ include _util-fns
|
|||
.l-sub-section
|
||||
:marked
|
||||
An Angular pipe is a function that transforms input values to output values for
|
||||
display in a [view](#view). We use the `#{atSym}Pipe` #{decorator}
|
||||
display in a [view](#view). We use the `#{atSym}Pipe` !{decorator}
|
||||
to associate the pipe function with a name. We then can use that
|
||||
name in our HTML to declaratively transform values on screen.
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 43 KiB |
Loading…
Reference in New Issue