Lightning Web コンポーネントで標準機能の連動選択リストを制御する

# 概要
LWC開発を進めて行く中で、オブジェクトに設定した選択リスト項目の連動関係をそのままコンポーネントのコンボボックスに反映する必要が出てくる場面があるかと思います。
選択値に変更が発生する度にコード側の修正を行う事はしたくないので、以前はApexからオブジェクトのSchemaからメタデータを取得する事で、標準で設定した連動関係を引っ張ってきていました。
ただLWCの開発者ガイドを見返していくと、ワイヤアダプタを使用する事で取り出せる事が発覚したので、実践していきたいと思います。

[getPicklistValues - Salesforce Lightning Component Library](https://developer.salesforce.com/docs/component-library/documentation/ja-jp/lwc/lwc.reference_wire_adapters_picklist_values)

# 項目設定
取引先オブジェクトに対して、「地方区分」項目と「都道府県」項目を作成します。
#### 地方区分(Api参照名:Regions__c)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/803801/e74a4a95-bd7e-3dfe-7f78-ccf4af2a76d0.png)

#### 都道府県(Api参照名:Prefectures__c)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/803801/7222db10-0b2c-6d2b-e110-875547b6bd21.png)

上記二つの選択リスト項目に対して、以下の連動関係を結んでいます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/803801/503a27dc-8d89-a2a9-6a11-91cef093181b.png)

# 全体コード

```html:lwc.html
<template>
    <lightning-card title="連動選択リスト" onclick={click}>
        <lightning-combobox
            label="地方区分"
            placeholder="-"
            options={regions}
            onchange={handleChangeRegions}>
        </lightning-combobox>
        <lightning-combobox
            label="都道府県"
            placeholder="-"
            options={prefectures}
            onchange={handleChangePrefectures}>
        </lightning-combobox>
    </lightning-card>
</template>
```

```js:lwc.js
import { LightningElement, wire } from 'lwc';
import { getObjectInfo, getPicklistValues } from 'lightning/uiObjectInfoApi';
import ACCOUNT_OBJECT from '@salesforce/schema/Account';
import PREFECTURES_FIELD from '@salesforce/schema/Account.Prefectures__c';

const MASTER = 'マスタ';

export default class ModuleTest extends LightningElement {
    @wire(getObjectInfo, { objectApiName: ACCOUNT_OBJECT })
    objectInfo;
    @wire(getPicklistValues, { recordTypeId: '$recordTypeId', fieldApiName: PREFECTURES_FIELD })
    prefecturesPicklist;

    recordTypeId;
    regions_value;
    prefectures_value;

    get regions(){
        if(this.prefecturesPicklist.data != undefined){
            const values = this.prefecturesPicklist.data.controllerValues;
            let list = [];
            for(let key in values){
                let result =  { label: key, value: String(values[key]) }
                list.push(result);
            }
            return list;
        }else{
            return null;
        }
    }
    get prefectures(){
        if(this.prefecturesPicklist.data != undefined){
            const values = this.prefecturesPicklist.data.values;
            const filterValues = values.filter(ele => {
                return ele.validFor[0] == this.regions_value;
            })
            let list = [];
            for(let i = 0; i < filterValues.length; i++){
                let result = { label: filterValues[i].value, value: filterValues[i].value }
                list.push(result);
            }
            return list;
        }else{
            return null;
        }
    }

    connectedCallback(){
        const rtis = this.objectInfo.data.recordTypeInfos;
        this.recordTypeId = Object.keys(rtis).find(rti => rtis[rti].name === MASTER);
    }
    handleChangeRegions(e){
        this.regions_value = Number(e.detail.value);
    }
    handleChangePrefectures(e){
        this.prefectures_value = e.detail.value
    }
}
```

# 解説
getPicklistValuesワイヤアダプタを使用するのに、レコードタイプIDが必要になるので、その取得から行っていきます。

```js
@wire(getObjectInfo, { objectApiName: ACCOUNT_OBJECT })
objectInfo;

connectedCallback(){
    const rtis = this.objectInfo.data.recordTypeInfos;
    this.recordTypeId = Object.keys(rtis).find(rti => rtis[rti].name === MASTER);
}
```
getObjectInfoワイヤアダプタで取得したobjectInfo変数の中身は以下のようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/803801/e33f91e3-4303-a886-4532-8313b7e4e91a.png)
こちらの中身からデフォルトのレコードタイプIDを取得しています。
詳細は以下の開発者ガイドを参照下さい。
[getObjectInfo - Salesforce Lightning Component Library](https://developer.salesforce.com/docs/component-library/documentation/ja-jp/lwc/lwc.reference_wire_adapters_object_info)

取得したレコードタイプIDを使用してgetPicklistValuesワイヤアダプタを実行します。

```js
@wire(getPicklistValues, { recordTypeId: '$recordTypeId', fieldApiName: PREFECTURES_FIELD })
prefecturesPicklist;
```

レコードタイプIDの引数をリアクティブで与えているので、connectedCallback()関数でrecordTypeIdに値が代入されたタイミングで実行されます。
取得したprefecturesPicklistの中身は以下のようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/803801/b838cb81-6196-84d1-490c-bcab3786734e.png)
連動項目がcontrolleValues, 制御項目がvaluesとして取得されていますね。
各valueの詳細をもう少し見ていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/803801/b3f8d0db-35f9-1fc8-59f2-b2a22744bfc4.png)
各連動元の値がvalidForに格納されています。今回は各連動項目値に対して重複する制御項目値はありませんでしたが、重複するものがある場合はここに連動元の値が配列が格納されていきます。

ここに格納されているcontrollerValuesを元にlightning-comboboxを作成し、選ばれた値に応じて必要なvaluesを取得するコンポーネントを作成していきます。

まずは地方区分側です。

```html
<lightning-combobox
    label="地方区分"
    placeholder="-"
    options={regions}
    onchange={handleChangeRegions}>
</lightning-combobox>
```

```js
get regions(){
    if(this.prefecturesPicklist.data != undefined){
        const values = this.prefecturesPicklist.data.controllerValues;
        let list = [];
        for(let key in values){
            let result =  { label: key, value: String(values[key]) }
            list.push(result);
        }
        return list;
    }else{
        return null;
    }
}

handleChangeRegions(e){
    this.regions_value = Number(e.detail.value);
}
```

getterでoption値を設定しています。
connectedCallbackが実行する前はprefecturesPicklistはまだ定義されていないので最初に条件分岐させています。
lightning-comboboxのoption値はlabelとvalueで構成された連想配列にする必要があるので、先ほど取得したcontrollerValuesのキーと値をそれぞれ必要な形に定義していきます。

ここで取得したregions_valueの値を元に都道府県選択リストを定義していきます。

```html
<lightning-combobox
    label="都道府県"
    placeholder="-"
    options={prefectures}
    onchange={handleChangePrefectures}>
</lightning-combobox>
```

```js
get prefectures(){
    if(this.prefecturesPicklist.data != undefined){
        const values = this.prefecturesPicklist.data.values;
        const filterValues = values.filter(ele => {
            return ele.validFor[0] == this.regions_value;
        })
        let list = [];
        for(let i = 0; i < filterValues.length; i++){
            let result = { label: filterValues[i].value, value: filterValues[i].value }
            list.push(result);
        }
        return list;
    }else{
        return null;
    }
}

handleChangePrefectures(e){
    this.prefectures_value = e.detail.value
}
```

filterメソッドを使って、prefecturesPicklistのvaluesから該当するvalidForのvalueのみを取り出し、先程と同じようにlightning-comboboxのoption値として定義していきます。

これで実装完了となります。以下のように標準機能で設定した連動関係で選択リストを作成する事が出来ました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/803801/08c61876-d0a2-3d2a-4f50-b2667942ff7c.png)

# 躓いた点・懸念事項
最初に実装した時、地方区分選択リストの値を選択しても該当の選択リスト値がセレクトされずにしばらく悩んでいました。
地方区分のoptionを設定している箇所で、valueをわざわざString型に変換しているのですが、どうやらNumber型でvalueを定義すると選択リストの選択リスト値をセレクト出来なくなるようです。(eventは動作する）

また、今回は地方区分選択リスト値の値とAPI参照名が同値のため、labelをそのまま取得すればAPI参照名として使用できます。
ただ、ラベルとAPI参照名を別の値で定義して運用している場合、API参照名の取得が出来なくなってしまいます。(prefecturesPicklistのcontrollerValuesのキーがvalueに定義する必要があるため)
その場合は別の実装方法を考える必要がありそうです。controllerValuesの値に数値ではなくAPI参照名が入ってくれれば楽なんですが……。

# 最後に
Apexでオブジェクト側に設定した連動関係を取得しようとした場合、とてつもなく面倒なコードになってしまうので、こんなに簡単に実装出来るのはすごく有難いですね。
私は試していないのですが、標準コンポーネントのlightning-input-fieldを使用すればVisualforceのようにそのまま連動関係を反映して選択リストを出してくれるのでしょうか？
もし、今回実装した内容より良い実装方法があれば是非コメント頂けると嬉しいです。