Angularの親子コンポーネント間での値の受け渡し

# はじめに

Angularを使い始めて１年弱になるのですが、親コンポーネントと子コンポーネント間での値の受け渡しについて理解ができていなく、開発の中で無駄な処理を書いていたことに気付きました。

大前提として、今回の記事はAngularを使用されている皆様にとって息をするくらい当たり前のことなので、
温かい目で見守ってくださると嬉しいです。。

今回の内容は、

* 親コンポーネントの変更を子コンポーネントに検知させる
* 子コンポーネントから親コンポーネントに値を渡す

の２つの解決策を備忘録として書いていきたいと思います。

# 本システムの概要

今回のケースは、
商品一覧画面の中で商品カテゴリーをプルダウンで選択すると、その時点で選択したカテゴリーに応じて一覧の内容が変更されるといったものです。

コンポーネントの構成としては、

* **item-list.component**：商品カテゴリーのプルダウンを配置するコンポーネント
* **item-list-table.component**：商品の一覧（テーブル）を表示するコンポーネント

といった形です。

また、説明のためにpresentationレベルのコンポーネントは作っておりませんので、
**\<mat-select>**といった部分はベタ書きしてます。

そもそものコンポーネントの分割や書き方がおかしい等、色々あると思いますが
前提条件としては、一旦目をつぶっていただけると幸いです。。。

<br>

それでは本題に入ります。

# 親コンポーネントの変更を子コンポーネントに検知させる

親コンポーネントで値を変更した際に、@Input() で子コンポーネントに値を渡すことはできるのですが
今回のケースのような、条件の変更時に一覧の更新ができるようにするには
その変更を子コンポーネント側で検知する必要があります。

そこで、以前までの私は

* ngOnChanges()
* EventEmitter

といったものを利用して無理やり子コンポーネント側に変更検知をさせていました。

<br>

ですが、そんな面倒なことをわざわざしなくても値の受け渡しくらいもっと簡単にできるはずだと
薄々気付いていました。

なんたってコンポーネント志向と言われるくらいですから
（公式ドキュメントを読め）

## 解決策

結論から言いますと、子コンポーネントのsetterを使いました

まず、商品一覧を表示する子コンポーネント

```ts:item-list-table.component.ts
// import省略

class Item {} 

@Component({
 selector: 'app-item-list-table',
 templateUrl: './item-list-table.component.html'
})
export class ItemListTable implements OnInit {
 itemList: Item[];
 @Input() set categoryId(categoryId: number) {  // @Input()でsetterを用意
  this.searchItems(categoryId).subscribe((items) => {
   // 省略
  });
 }

 searchItems(categoryId: number): Observable<Item[]> {
  // 選択したカテゴリーの商品をDBから取得し一覧に反映させる処理
 } 
}
```

これで値を渡しつつ任意の処理が実行できるというわけです。

次に親コンポーネント
ここで子コンポーネントのsetterに値を渡す処理を書きます。

```html:item-list.component.html
<!-- 商品カテゴリ用プルダウン -->
<mat-form-field>
 <!-- selectionChangeイベントで、プロパティに選択中のカテゴリーIDをセットする -->
 <mat-select [formControl]="control" (selectionChange)="selectCategory()">
  <mat-option *ngFor="let category of categories" [value]="category.id">
   {{ category.name }}
  </mat-option>
 </mat-select>
</mat-form-field>

<!-- 商品一覧 -->
<!-- ここで子コンポーネントのsetterを参照し、選択中のカテゴリーIDを渡す -->
<app-item-list-table [categoryId]="selectedCategoryId"></app-item-list-table>
```

mat-selectのselectionChangeは、選択に変更があった時にイベントが発火します。
なのでカテゴリーの変更時、
親コンポーネントのプロパティに選択中のカテゴリーIDをセットします。

```ts:item-list.component.ts
// import省略

@Component({
 selector: 'app-item-list',
 templateUrl: './item-list.component.html'
})
export class ItemListComponent implements OnInit {
 selectedCategoryId?: string;
 control?: FormControl;
 categories = [
  { id: 0, name: 'カテゴリー1'},
  { id: 1, name: 'カテゴリー2'},
  { id: 2, name: 'カテゴリー3'}
 ];

 selectCategory(): void {
  this.selectedCategoryId = this.control.value;
 }
}
```

選択中のカテゴリーIDがセットされているプロパティを、子コンポーネントのsetterに渡してあげることで完了です。

# 子コンポーネントから親コンポーネントに値を渡す

今回は商品一覧を表示する、子コンポーネントのitemListの要素数を使って、
親コンポーネント側で「～件」と表示したいわけです。

これもわざわざEventEmitterやngOnChanges()を使ってゴリゴリ無駄な処理を書いていましたが、
一瞬で書くことができるんですね。

## 解決策

Angularでは、親コンポーネントから子コンポーネントのプロパティやメソッドを参照することができるんですって！！

いやーーーすごい！！！！

<br>
<br>

誠に恥ずかしながら、本気で知りませんでした。。

<br>

まず、商品一覧を表示する子コンポーネント
ここのプロパティ「itemList」を参照したいです。

```ts:item-list-table.component.ts
// import省略

class Item {} 

export class ItemListTable implements OnInit {
 itemList: Item[]; // これが欲しい
 @Input() set categoryId(categoryId: number) { 
  this.searchItems(categoryId).subscribe((items) => {
   // 省略
  });
 }

 // 省略
}
```

そして親コンポーネント

\<app-～>で記述している子コンポーネントのタグ内で、「#」をつけた変数を宣言することで
そのタグのコンポーネントに対するアクセサになってくれるんです。

```html:item-list.component.html
<!-- プルダウン省略 -->

<!-- 商品一覧 -->
<!-- #itemListTable これにより、指定したコンポーネントのアクセサとして使用可能 -->
<app-item-list-table #itemListTable></app-item-list-table>
<!-- プロパティを参照 -->
<span>{{ itemListTable.itemList.length }} 件を表示</span>
```

無事にプロパティを参照することができました。

他にもメソッドなども参照できるみたいです。

# 追記(2021年8月9日(月))

1つ目の「親の変更を子に検知させる」方法についてですが、子で表示させたいデータを親で取得した後に、@Input経由でそのまま渡すのが実装方法としては無難なのかな…と思いました。

# 終わりに

本当に、知らなかったことが恥ずかしいくらい当たり前のことだと思いますが、
以前まで苦手意識が強かった処理が手軽に書けるようになったのは嬉しいです。

あとは公式ドキュメントをちゃんと読みます。。

最後まで読んでいただきありがとうございました。