LoginSignup
129
127

More than 5 years have passed since last update.

コンポーネント間のデータ授受メモ

Last updated at Posted at 2016-10-08

参考元:
COMPONENT INTERACTION

親から子に情報を与える場合

親の変化を子で何かするときに使用します。

Inputを使用する

公式
子コンポーネントでは親から取得したデータを格納するプロパティに@Input()デコレーションを定義します。親のテンプレートの[]で指定した値をそのまま使用する場合は、引数なしの@Input() プロパティ名 :型で良いですが、変える場合は@Input('ほにゃらら') プロパティ名 :型にします。
(今回の場合親のテンプレートでは[master]と定義していますが、子ではmasterNameとして使用します。)

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;
}

親コンポーネントのテンプレートで値を渡します。
[ほにゃらら]部分に、子のプロパティ名を指定し、右辺に渡すプロパティや、定数を指定します。
(文字列を渡す時は[ほにゃらら]="'hogehoge'"の様に、シングルクォートを忘れない様にしましょう)

import { Component } from '@angular/core';
import { HEROES } from './hero';
@Component({
  selector: 'hero-parent',
  template: `
    <h2>{{master}} controls {{heroes.length}} heroes</h2>
    <hero-child *ngFor="let hero of heroes"
      [hero]="hero"
      [master]="master">
    </hero-child>
  `
})
export class HeroParentComponent {
  heroes = HEROES;
  master: string = 'Master';
}

アクセサを使う

公式
アクセサ(get/set)を使うこともできます。プロパティをprivateにしたかったり、例の様にトリムする場合などに使用します。
(普通のgetter/setterでもいいですが。)

import { Component, Input } from '@angular/core';
@Component({
  selector: 'name-child',
  template: `
    <h3>"{{name}}"</h3>
  `
})
export class NameChildComponent {
  private _name: string = '<no name set>';
  @Input()
  set name(name: string) {
    this._name = (name && name.trim()) || '<no name set>';
  }
  get name() { return this._name; }
}
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  '];
}

ngOnChangesを使う

公式
ngOnChangesメソッドを使って、変更を取得します。@Input()のデータバインド後に呼び出されるメソッドです。ngOnChangesはライフサイクルフックの一つです。
@Inputで、親から取得したプロパティをキーに、サービスから値を取得したりするときに使用できます。
(ソースは公式を参照ください。)

親が子を監視してバインドする場合

公式

子の変化を親で何かするときに使用します。

子供のイベント監視(@OutputEventEmitter)

子側でEventEmitterを使います。
EventEmitterは出力プロパティで、普通は@Outputデコレータと一緒に使います。

子コンポーネントから親コンポーネントにデータを渡す例(公式より拝借):

Mr. IQなどの名前の定義及び、カウンターは親(VoteTakerComponent)で行い、
ボタン関連は子(VoterComponent)で定義しています。
子のボタンがクリックされると、子のvote()メソッドが呼び出され、メソッド内で親のonVoted()メソッドに伝え(emit)、親がカウントアップします。
(循環参照ぽい気がする動きですが、詳しいことはわかりません。)

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;
  }
}
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++;
  }
}

@Input@Outputを使用して双方向バインドする

正しいやり方か判りませんが、@Input@Outputを使用して双方向バインドします。
子ではinputプロパティで親からの値を取得し、outputプロパティに値を入れ、親に渡します。

import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({~~省略~~})
export class Child{

  @Input()  input:bool
  @Output() output = new EventEmitter<bool>();

  toFalse(){
    this.input = false;
    this.output.emit(false);
  }
}
import { Component } from '@angular/core';

@Component({
  selector: 'parent',
  template = `<子のセレクタ
    [input]="bool"
    (output)="bool = $event">
  </子のセレクタ>`
})

export class Parent{
  private bool:boolean;
  ~~省略~~
}

親が子供のプロパティやメソッドを使う

公式

親側のテンプレートで、子コンポーネントをローカル変数化します。

子(特に注目すべき点はなさそう)
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);
  }
}

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 { }

子のメソッドstart(),stop()及び、子のプロパティsecondsを使用しています。

親コンポーネントから子供のメソッドを使用する

公式
先述のパターンは簡単ですが、テンプレート内だけで、親クラス自体が子クラスにアクセスできるわけではないため、子の変数や関数の参照を親のクラスで使用することはできません。
親側でViewChildを使用すればそれができます。

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(); }
}

window.setTimeout
指定された遅延の後に、コードの断片または関数を実行します。

要点は@ViewChildデコレータと,ngAfterViewInitライフサイクルフックです。
@ViewChildデコレータを通して、子のCountdownTimerComponentをprivateなtimerComponentにインジェクトしています。
本来親コンポーネントが作成されてから子コンポーネントが作成されるため、this.secondsは取得に失敗しエラーが発生しますが、ngAfterViewInitライフサイクルフックにより、子コンポーネント表示後0ミリ秒遅延させ、取得させることで問題を解決させています。

serviceを使って親子でデータをやり取りする

公式

バインドが必要なコンポーネントでサービスをsubscribeすることにより、データを共有します。

サービス
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);
  }
}
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; }
  }
}
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();
  }
}
129
127
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
129
127