Angular 2
コンポーネントのやりとり
サーチ…
前書き
異なるディレクティブとコンポーネント間で情報を共有する。
入力バインディングで親から子へデータを渡す
HeroChildComponentには2つの入力プロパティがあり、通常は@Inputデコレーションで装飾されています。
import { Component, Input } from '@angular/core';
import { Hero } from './hero';
@Component({
selector: 'hero-child',
template: `
<h3>{{hero.name}} says:</h3>
<p>I, {{hero.name}}, am at your service, {{masterName}}.</p>
`
})
export class HeroChildComponent {
@Input() hero: Hero;
@Input('master') masterName: string;
}
セッターで入力プロパティの変更を傍受する
入力プロパティーを使用して、親からの値を代入して処理します。
子のNameChildComponentの名前入力プロパティーの設定子は、名前から空白を切り取り、空の値をデフォルトのテキストに置き換えます。
import { Component, Input } from '@angular/core';
@Component({
selector: 'name-child',
template: '<h3>"{{name}}"</h3>'
})
export class NameChildComponent {
private _name = '';
@Input()
set name(name: string) {
this._name = (name && name.trim()) || '<no name set>';
}
get name(): string { return this._name; }
}
すべてのスペースを含む名前を含む名前のバリエーションを示すNameParentComponentは次のとおりです。
import { Component } from '@angular/core';
@Component({
selector: 'name-parent',
template: `
<h2>Master controls {{names.length}} names</h2>
<name-child *ngFor="let name of names" [name]="name"></name-child>
`
})
export class NameParentComponent {
// Displays 'Mr. IQ', '<no name set>', 'Bombasto'
names = ['Mr. IQ', ' ', ' Bombasto '];
}
親は子イベントをリスンする
子コンポーネントは、イベントが発生したときにイベントを発生させるEventEmitterプロパティを公開します。親はそのイベントプロパティにバインドし、それらのイベントに反応します。
子のEventEmitterプロパティは出力プロパティです。通常、このVoterComponentで見られる@Outputデコレーションで装飾されています。
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'my-voter',
template: `
<h4>{{name}}</h4>
<button (click)="vote(true)" [disabled]="voted">Agree</button>
<button (click)="vote(false)" [disabled]="voted">Disagree</button>
`
})
export class VoterComponent {
@Input() name: string;
@Output() onVoted = new EventEmitter<boolean>();
voted = false;
vote(agreed: boolean) {
this.onVoted.emit(agreed);
this.voted = true;
}
}
ボタンをクリックすると、trueまたはfalse(ブーリアンペイロード)の放出がトリガーされます。
親VoteTakerComponentは、子イベントペイロード($ event)に応答し、カウンタを更新するイベントハンドラ(onVoted)をバインドします。
import { Component } from '@angular/core';
@Component({
selector: 'vote-taker',
template: `
<h2>Should mankind colonize the Universe?</h2>
<h3>Agree: {{agreed}}, Disagree: {{disagreed}}</h3>
<my-voter *ngFor="let voter of voters"
[name]="voter"
(onVoted)="onVoted($event)">
</my-voter>
`
})
export class VoteTakerComponent {
agreed = 0;
disagreed = 0;
voters = ['Mr. IQ', 'Ms. Universe', 'Bombasto'];
onVoted(agreed: boolean) {
agreed ? this.agreed++ : this.disagreed++;
}
}
親はローカル変数を介して子と対話する
親コンポーネントは、データバインディングを使用して子プロパティを読み込んだり、子メソッドを呼び出すことはできません。次の例に示すように、子要素のテンプレート参照変数を作成し、その変数を親テンプレート内で参照することで、両方を行うことができます。
CountdownTimerComponentは、カウントダウンを0にしてロケットを起動します。それは、時計を制御する開始と停止のメソッドを持っており、独自のテンプレートにカウントダウンステータスメッセージを表示します。
import { Component, OnDestroy, OnInit } from '@angular/core';
@Component({
selector: 'countdown-timer',
template: '<p>{{message}}</p>'
})
export class CountdownTimerComponent implements OnInit, OnDestroy {
intervalId = 0;
message = '';
seconds = 11;
clearTimer() { clearInterval(this.intervalId); }
ngOnInit() { this.start(); }
ngOnDestroy() { this.clearTimer(); }
start() { this.countDown(); }
stop() {
this.clearTimer();
this.message = `Holding at T-${this.seconds} seconds`;
}
private countDown() {
this.clearTimer();
this.intervalId = window.setInterval(() => {
this.seconds -= 1;
if (this.seconds === 0) {
this.message = 'Blast off!';
} else {
if (this.seconds < 0) { this.seconds = 10; } // reset
this.message = `T-${this.seconds} seconds and counting`;
}
}, 1000);
}
}
タイマーコンポーネントをホストするCountdownLocalVarParentComponentを見てみましょう。
import { Component } from '@angular/core';
import { CountdownTimerComponent } from './countdown-timer.component';
@Component({
selector: 'countdown-parent-lv',
template: `
<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>
`,
styleUrls: ['demo.css']
})
export class CountdownLocalVarParentComponent { }
親コンポーネントは、子の開始および終了メソッドまたは秒プロパティにデータをバインドできません。
子コンポーネントを表すタグ()にローカル変数(#timer)を置くことができます。これは、子コンポーネント自体への参照と、親テンプレート内からそのプロパティまたはメソッドのいずれかにアクセスする機能を提供します。
この例では、親のボタンを子の開始点に接続して停止し、補間を使用して子の秒のプロパティを表示します。
ここでは、親と子が一緒に働いているのを見ています。
親はViewChildを呼び出します
ローカル変数のアプローチは簡単で簡単です。しかし、親子配線は親テンプレート内で完全に行われなければならないため、限られています。親コンポーネント自体は子へのアクセス権を持ちません。
親コンポーネントクラスのインスタンスが子コンポーネントの値を読み書きする必要がある場合、または子コンポーネントメソッドを呼び出す必要がある場合は、ローカル変数技法を使用できません。
親コンポーネントクラスがその種のアクセスを必要とするとき、子コンポーネントをViewChildとして親に注入します。
このテクニックは、同じカウントダウンタイマーの例で説明します。私たちはその外観や行動を変更しません。子のCountdownTimerComponentも同様です。
私たちはデモのためだけに、ローカル変数からViewChildテクニックに切り替える予定です。ここに親のCountdownViewChildParentComponentがあります:
import { AfterViewInit, ViewChild } from '@angular/core';
import { Component } from '@angular/core';
import { CountdownTimerComponent } from './countdown-timer.component';
@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>
`,
styleUrls: ['demo.css']
})
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(); }
}
親コンポーネントクラスに子ビューを取得するにはもう少し作業が必要です。
ViewChildデコレータとAfterViewInitライフサイクルフックへの参照をインポートします。
@ViewChildプロパティデコレーションを使用して、子のCountdownTimerComponentをプライベートtimerComponentプロパティに注入します。
#timerローカル変数はコンポーネントのメタデータから削除されました。その代わりに、ボタンを親コンポーネントの独自の開始および終了メソッドにバインドし、親コンポーネントのsecondsメソッドの周りに補間を適用します。
これらのメソッドは、注入されたタイマーコンポーネントに直接アクセスします。
ngAfterViewInitライフサイクルフックは重要なしわです。 Timularコンポーネントは、Angularが親ビューを表示するまで使用できません。したがって、最初は0秒を表示します。
次に、AngularはngAfterViewInitライフサイクルフックを呼び出し、親ビューのカウントダウン秒の表示を更新するのが遅すぎます。 Angularの単方向データフロールールは、同じサイクルで親ビューを更新できないようにします。秒を表示するには、1ターン待たなければなりません。
setTimeoutを使用して1ティックを待ち、次にtimerコンポーネントから将来の値を取得するようにsecondsメソッドを修正します。
親と子はサービスを介して通信する
親コンポーネントとその子コンポーネントは、そのインタフェースがそのファミリ内で双方向通信を可能にするサービスを共有する。
サービスインスタンスのスコープは、親コンポーネントとその子です。このコンポーネントのサブツリー外のコンポーネントは、サービスまたはその通信にアクセスできません。
このMissionServiceは、MissionControlComponentを複数のAstronautComponent子に接続します。
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
@Injectable()
export class MissionService {
// Observable string sources
private missionAnnouncedSource = new Subject<string>();
private missionConfirmedSource = new Subject<string>();
// Observable string streams
missionAnnounced$ = this.missionAnnouncedSource.asObservable();
missionConfirmed$ = this.missionConfirmedSource.asObservable();
// Service message commands
announceMission(mission: string) {
this.missionAnnouncedSource.next(mission);
}
confirmMission(astronaut: string) {
this.missionConfirmedSource.next(astronaut);
}
}
MissionControlComponentは、子(プロバイダのメタデータ配列を介して)と共有するサービスのインスタンスを提供し、そのインスタンスをそのコンストラクタを通じて自身に注入します。
import { Component } from '@angular/core';
import { MissionService } from './mission.service';
@Component({
selector: 'mission-control',
template: `
<h2>Mission Control</h2>
<button (click)="announce()">Announce mission</button>
<my-astronaut *ngFor="let astronaut of astronauts"
[astronaut]="astronaut">
</my-astronaut>
<h3>History</h3>
<ul>
<li *ngFor="let event of history">{{event}}</li>
</ul>
`,
providers: [MissionService]
})
export class MissionControlComponent {
astronauts = ['Lovell', 'Swigert', 'Haise'];
history: string[] = [];
missions = ['Fly to the moon!',
'Fly to mars!',
'Fly to Vegas!'];
nextMission = 0;
constructor(private missionService: MissionService) {
missionService.missionConfirmed$.subscribe(
astronaut => {
this.history.push(`${astronaut} confirmed the mission`);
});
}
announce() {
let mission = this.missions[this.nextMission++];
this.missionService.announceMission(mission);
this.history.push(`Mission "${mission}" announced`);
if (this.nextMission >= this.missions.length) { this.nextMission = 0; }
}
}
また、AstronautComponentはコンストラクタにサービスを挿入します。各AstronautComponentはMissionControlComponentの子であるため、親のサービスインスタンスを受け取ります。
import { Component, Input, OnDestroy } from '@angular/core';
import { MissionService } from './mission.service';
import { Subscription } from 'rxjs/Subscription';
@Component({
selector: 'my-astronaut',
template: `
<p>
{{astronaut}}: <strong>{{mission}}</strong>
<button
(click)="confirm()"
[disabled]="!announced || confirmed">
Confirm
</button>
</p>
`
})
export class AstronautComponent implements OnDestroy {
@Input() astronaut: string;
mission = '<no mission announced>';
confirmed = false;
announced = false;
subscription: Subscription;
constructor(private missionService: MissionService) {
this.subscription = missionService.missionAnnounced$.subscribe(
mission => {
this.mission = mission;
this.announced = true;
this.confirmed = false;
});
}
confirm() {
this.confirmed = true;
this.missionService.confirmMission(this.astronaut);
}
ngOnDestroy() {
// prevent memory leak when component destroyed
this.subscription.unsubscribe();
}
}
AstronautComponentが破棄された時点で、サブスクリプションを取得し、登録を解除することに注意してください。これはメモリリークガードのステップです。 AstronautComponentのライフタイムはアプリ自体のライフタイムと同じなので、このアプリには実際のリスクはありません。それはもっと複雑なアプリケーションでは必ずしも真実ではありません。
このガードは、親としてMissionServiceのライフタイムを制御するため、MissionControlComponentには追加しません。 Historyログは、メッセージが親MissionControlComponentとAstronautComponentの子の間で両方向に移動し、サービスによって容易になることを示します。