Professional Documents
Culture Documents
information.
@Input() decorator.
component-interaction/src/app/hero-child.component.ts
{ , } '@angular∕core';
{ } '.∕hero';
@Component({
selector: 'app-hero-child',
: `
<h3>{{hero.name}} says:<∕h3>
<p>I, {{hero.name}}, am at your service,
{{masterName}}.<∕p>
`
})
{
@Input() hero!: ;
masterName as 'master' .
child's master alias, and each iteration's hero instance to the child's
hero property.
component-interaction/src/app/hero-parent.component.ts
{ } '@angular∕core';
{ HEROES } '.∕hero';
@Component({
selector: 'app-hero-parent',
: `
<h2>{{master}} controls {{heroes.length}}
heroes<∕h2>
<app-hero-child
*ngFor="let hero of heroes"
[hero]="hero"
[master]="master">
<∕app-hero-child>
`
})
{
heroes = HEROES;
master = 'Master';
}
∕∕ ...
heroNames = ['Dr. IQ', 'Magneta', 'Bombasto'];
masterName = 'Master';
Back to top
parent.
{ , } '@angular∕core';
@Component({
selector: 'app-name-child',
: '<h3>"{{name}}"<∕h3>'
})
{
@Input()
name(): { ._name; }
name(name: ) {
._name = (name && name.trim()) || '<no name
set>';
}
_name = '';
}
component-interaction/src/app/name-parent.component.ts
{ } '@angular∕core';
@Component({
selector: 'app-name-parent',
: `
<h2>Master controls {{names.length}} names<∕h2>
∕∕ ...
it('should display trimmed, non-empty names',
() => {
nonEmptyNameIndex = 0;
nonEmptyName = '"Dr. IQ"';
parent = element( .tagName('app-name-
parent'));
hero = parent.all( .tagName('app-name-
child')). (nonEmptyNameIndex);
displayName =
hero.element( .tagName('h3')).getText();
expect(displayName).toEqual(nonEmptyName);
});
displayName =
hero.element( .tagName('h3')).getText();
expect(displayName).toEqual(defaultName);
});
∕∕ ...
Back to top
changes:
component-interaction/src/app/version-child.component.ts
{ , , ,
} '@angular∕core';
@Component({
selector: 'app-version-child',
: `
<h3>Version {{major}}.{{minor}}<∕h3>
<h4>Change log:<∕h4>
<ul>
<li *ngFor="let change of changeLog">{{change}}
<∕li>
<∕ul>
`
})
@Input() major = 0;
@Input() minor = 0;
changeLog: [] = [];
ngOnChanges(changes: ) {
log: [] = [];
( propName changes) {
changedProp = changes[propName];
to =
JSON.stringify(changedProp.currentValue);
(changedProp.isFirstChange()) {
log.push(`Initial value of ${propName} set to
${to}`);
} {
=
JSON.stringify(changedProp.previousValue);
log.push(`${propName} changed from ${from} to
${to}`);
}
}
.changeLog.push(log. (', '));
}
}
component-interaction/src/app/version-parent.component.ts
{ } '@angular∕core';
@Component({
selector: 'app-version-parent',
: `
<h2>Source code version<∕h2>
<button type="button" (click)="newMinor()">New
minor version<∕button>
<button type="button" (click)="newMajor()">New
major version<∕button>
<app-version-child [major]="major"
[minor]="minor"><∕app-version-child>
`
})
{
major = 1;
minor = 23;
newMinor() {
.minor++;
}
newMajor() {
.major++;
.minor = 0;
}
}
Test that both input properties are set initially and that button clicks
∕∕ ...
∕∕ Test must all execute in this exact order
it('should set expected initial values', () =>
{
actual = getActual();
expect(actual.label).toBe(initialLabel);
expect(actual.count).toBe(1);
expect(
actual.logs. (0).getText()).toBe(initialLog);
});
newMinorButton.click();
newMinorButton.click();
actual = getActual();
expect(actual.label).toBe(labelAfter2Minor);
expect(actual.count).toBe(3);
expect(
actual.logs. (2).getText()).toBe(logAfter2Minor);
});
newMajorButton.click();
actual = getActual();
expect(actual.label).toBe(labelAfterMajor);
expect(actual.count).toBe(2);
expect(
actual.logs. (1).getText()).toBe(logAfterMajor);
});
getActual() {
versionTag = element( .tagName('app-version-
child'));
label =
versionTag.element( .tagName('h3')).getText();
ul = versionTag.element(( .tagName('ul')));
logs = ul.all( .tagName('li'));
{
label,
logs,
count: logs.count(),
};
}
∕∕ ...
Back to top
emits events when something happens. The parent binds to that event
component-interaction/src/app/voter.component.ts
{ , , , }
'@angular∕core';
@Component({
selector: 'app-voter',
: `
<h4>{{name}}<∕h4>
<button type="button" (click)="vote(true)"
[disabled]="didVote">Agree<∕button>
<button type="button" (click)="vote(false)"
[disabled]="didVote">Disagree<∕button>
`
})
{
@Input() name = '';
vote(agreed: ) {
.voted.emit(agreed);
.didVote = ;
}
}
payload.
component-interaction/src/app/votetaker.component.ts
{ } '@angular∕core';
@Component({
selector: 'app-vote-taker',
: `
<h2>Should mankind colonize the Universe?<∕h2>
<h3>Agree: {{agreed}}, Disagree: {{disagreed}}
<∕h3>
<app-voter
*ngFor="let voter of voters"
[name]="voter"
(voted)="onVoted($event)">
<∕app-voter>
`
})
{
agreed = 0;
disagreed = 0;
voters = ['Dr. IQ', 'Celeritas', 'Bombasto'];
onVoted(agreed: ) {
(agreed) {
.agreed++;
} {
.disagreed++;
}
}
}
appropriate counters:
component-interaction/e2e/src/app.e2e-spec.ts
∕∕ ...
it('should not emit the event initially', () =>
{
voteLabel = element( .tagName('app-vote-
taker')).element( .tagName('h3'));
expect( voteLabel.getText()).toBe('Agree: 0,
Disagree: 0');
});
agreeButton1.click();
expect( voteLabel.getText()).toBe('Agree: 1,
Disagree: 0');
});
agreeButton1.click();
expect( voteLabel.getText()).toBe('Agree: 0,
Disagree: 1');
});
∕∕ ...
Back to top
Parent interacts with child using
local variable
A parent component cannot use data binding to read child properties or
for the child element and then reference that variable within the parent
counts down to zero and launches a rocket. The start and stop
{ , }
'@angular∕core';
@Component({
selector: 'app-countdown-timer',
: '<p>{{message}}<∕p>'
})
{
message = '';
seconds = 11;
ngOnDestroy() { .clearTimer?.(); }
start() { .countDown(); }
stop() {
.clearTimer?.();
.message = `Holding at T-${this.seconds}
seconds`;
}
clearTimer: | ;
countDown() {
.clearTimer?.();
interval = setInterval(() => {
.seconds -= 1;
( .seconds === 0) {
.message = 'Blast off!';
} {
( .seconds < 0) { .seconds = 10; }
∕∕ reset
.message = `T-${this.seconds} seconds and
counting`;
}
}, 1000);
.clearTimer = () => clearInterval(interval);
}
}
The CountdownLocalVarParentComponent that hosts the timer
component is as follows:
component-interaction/src/app/countdown-parent.component.ts
{ } '@angular∕core';
{ }
'.∕countdown-timer.component';
@Component({
selector: 'app-countdown-parent-lv',
: `
<h3>Countdown to Liftoff (via local variable)
<∕h3>
<button type="button"
(click)="timer.start()">Start<∕button>
<button type="button"
(click)="timer.stop()">Stop<∕button>
<div class="seconds">{{timer.seconds}}<∕div>
<app-countdown-timer #timer><∕app-countdown-
timer>
`,
styleUrls: ['..∕assets∕demo.css']
})
{ }
The parent component cannot data bind to the child's start and stop
This example wires parent buttons to the child's start and stop and
seconds displayed in the child's status message. Test also that clicking
∕∕ ...
∕∕ The tests trigger periodic asynchronous operations
(via `setInterval()`), which will prevent
∕∕ the app from stabilizing. See https:∕∕angular.io
∕api∕core∕ApplicationRef#is-stable-examples
∕∕ for more details.
∕∕ To allow the tests to complete, we will disable
automatically waiting for the Angular app to
∕∕ stabilize.
beforeEach(() =>
browser.waitForAngularEnabled( ));
afterEach(() => browser.waitForAngularEnabled( ));
startButton.click();
expect( timer.getText()).toContain(
seconds.getText());
});
startButton.click();
expect(
timer.getText()). .toContain('Holding');
stopButton.click();
expect( timer.getText()).toContain('Holding');
});
∕∕ ...
Back to top
Parent calls an
The local variable approach is straightforward. But it is limited because
the parent-child wiring must be done entirely within the parent template.
You can't use the local variable technique if the parent component's
Because the class instances are not connected to one another, the
parent class cannot access the child class properties and methods.