Angularで1:Nのコンポーネント間でデータ連携を行う

はじめに

AngularのComponent間のデータ連携はいくつか手法があります。
1:1のComponent間であれば @Input @Output で事足りていましたが、1つのComponentで入力されたデータを加工し、複数のComponentで拾い、表示しようと考えた時に少し冗長なコードになってしまいます。
困った時の公式ドキュメント:コンポーネントの相互作用Service を利用した例が書かれていたのでまとめてみます。

設計概要

今回はappComponentのFormから入力したデータをvalueChangeで監視しdataServiceに渡します。
dataServiceでは共通のデータ処理としてkey3にtrueフラグを立てnext() で各Componentへデータを渡します。
個別の表示を目的とした処理として片方のComponentでは足し算、もう片方では引き算を行い、key3がtrueであれば表示します。

component data flow

Angularで実装

まずはデータ型を規定します。

export class Data {
  key1: number;
  key2: number;
  key3: boolean;
  result: number;
}

次にデータの入力箇所を実装します。
formGroupとしてkey1/key2のformを規定しそれとなくバリデーションしておきます。

dataFormGroup = this.formBuilder.group({
    key1: [0, Validators.required],
    key2: [0, Validators.required],
  });

Templateは以下の感じです。

<form [formGroup]="dataFormGroup">
  key1: <input  placeholder="key1" type="number" formControlName="key1" min="0"><br>
  key2: <input  placeholder="key2" type="number" formControlName="key2" min="0">
</form>

Templateから入力された値をvalueChanges で監視しsubscribe()します。
その結果をServiceへ渡します。

ngOnInit() {
  this.dataFormGroup.valueChanges.subscribe(
    (value: Data) => {
      this.dataService.recieve(value);
    }
  );
}

さて、いよいよ本題ですが 任意のタイミングでデータのやりとりが可能なSubjectというクラスを利用します。
今回はobserverをdataSubject、ObservableをdataStateとして宣言しています。
また、共通の処理として各Componentにnextする前にdata.key3 = true とフラグを立てておきます。

export class DataService {
  public dataSubject = new Subject<Data>();
  public dataState = this.dataSubject.asObservable();

  constructor() { }

  recieve(data: Data) {
    data.key3 = true
    this.dataSubject.next(data);
  }
}

あとは各Componentでsubscribeし個別の設定として足し算処理を行います。

export class Receive01Component implements OnInit {
  data = {} as Data;

  constructor(
    private dataService: DataService,
  ) { }

  ngOnInit() {
    this.dataService.dataState.subscribe(
      (data: Data) => {
        this.addition(data);
      }
    )
  }

  addition(data: Data) {
    this.data.key3 = data.key3;
    this.data.result = data.key1 + data.key2;
  }
}

結果をTemplateに表示します。
*ngIf="data.key3 としてフラグをみて表示可否を切り替えることができます。

<div>receive01:</div>
<div *ngIf="data.key3; else elseBlock">{{data.result}}</div>
<ng-template #elseBlock>0</ng-template>

stackblitzを貼っておきます。

stackblitz.com

まとめ

Angularで1:Nのコンポーネント間でデータ連携を行う方法をまとめました。
このくらいのデータ量と処理であれば@Input @Outputでやった方が簡単ですが、共通処理が大きくなればなるほどコード量が少なくなるはずです。

参考にさせていただいたページ

わからなかったこと

1度の入力でvalueChanges が2回発火してしまう。
関係してそうなissueは見つけたが解決できず。