【Salesforce】動的インタラクションを利用したLightningコンポーネント間の連携の実装方法

## はじめに
現在Salesforceではノーコード・ローコード開発ができる多くの機能が実装されています。最近では動的フォーム、動的アクション、動的関連リストなど動的〇〇シリーズにより、今までは個別開発が必要だった要件を設定ベースで実装することができるようになりました。今回は動的シリーズの中でもまだあまり活用が一般化していない<b>動的インタラクション</b>（Dynamic Interaction）を紹介しようと思います。とても便利で将来性のある機能です。本稿の執筆時点で日本語で詳しく解説された記事をほとんど見かけなかったので、皆様の活用の一助となればと思います。

## 動的インタラクションとは
動的インタラクションは<b>Lightningコンポーネント間の連携をLightningアプリケーションビルダーで設定できる機能</b>です。今までコンポーネント間の連携は何かしらのコーディングをすることでしか実装できませんでした。動的インタラクションはその連携処理の一部を標準設定で実装できるようにした機能です。今回は以下のようなサンプルアプリを作って動的インタラクションを解説していきます。
![sfac2022.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878697/db6f768a-285d-6a0d-8af8-2223d4c6ac64.gif)

取引先一覧コンポーネント（画面左）が連携元となるコンポーネント。取引先詳細コンポーネント（画面右）が連携先となるコンポーネントです。一覧の取引先を選択するとその詳細が表示されるという動作となります。2つのコンポーネントは完全に分離されたコンポーネントであり、一覧の取引先が選択されたときに、詳細のコンポーネントへ一覧で選択された取引先の情報（ここでは取引先ID）を連携する必要があります。コンポーネント間の連携を実装するにはいくつか方法があります。今まではSalesforce標準のLightning Message Service（以下LMS）で実装するのがメジャーな方法の1つでした。今回はLMSを使わずコンポーネント間連携を実装します。

## LightningWebコンポーネントの定義

- 取引先一覧コンポーネント
まずは連携元となる取引先一覧コンポーネントの定義です。
```xml:accountListWithDI.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>55.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__AppPage">
            <event name="itemselected" label="Item Selected">
                <schema>
                     {   
                       "type": "object",   
                       "properties": {   
                         "recordId": {   
                           "type": "string",
                           "title": "Record Id"
                         }   
                       }   
                     }   
                 </schema>   
            </event>   
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>
```
動的インタラクションを設定する上で重要となるコンポーネント設定ファイルです。
```targetConfig```でイベントを定義します。ここでは取引先一覧が選択されたイベントとして```itemselected```イベントを定義し、イベントの情報として渡す取引先IDを格納する項目として```recordId```を定義しています。 
```xml:accountListWithDI.html
<template>
    <div>
        <table class="slds-table slds-table_cell-buffer slds-table_bordered">
            <thead>
                <tr class="slds-line-height_reset">
                    <th class="" scope="col">
                        <div class="slds-truncate" title="Account Name">Account Name</div>
                    </th>
                </tr>
            </thead>
            <tbody>
                <template for:each={records} for:item="record">
                    <tr key={record.Id} class="slds-hint-parent">
                        <td>
                            <div class="slds-truncate">
                                <a data-record-id={record.Id} onclick={handleClick}>
                                    {record.Name}
                                </a>    
                            </div>
                        </td>                
                    </tr>
                </template>            
            </tbody>
        </table>        
    </div>    
</template>
```
続いて画面定義です。取引先一覧となるテーブルを記述しています。テーブルの行ではonclickイベントを定義しておきます。またどの行がクリックされたかを識別するために```data-record-id```属性で取引先のIDを指定しています。
```JavaScript:accountListWithDI.js
import { LightningElement, wire} from 'lwc';
import getAccounts from '@salesforce/apex/AccountListService.getAccounts';
export default class AccountListWithDI extends LightningElement {
    records;
    @wire(getAccounts)
    wiredRecords({ data, error }) {
        if (!data) return;
        this.records = data;    
    }
    handleClick(event) {
        // 一覧の取引先選択のイベントから取引先IDを取得
        const recordId = event.target.getAttribute('data-record-id');
        // カスタムイベントを生成
        const selectedEvent = new CustomEvent('itemselected', {detail : {recordId : recordId}});
        this.dispatchEvent(selectedEvent);    
    }
}
```
続いてJavaScriptコードです。一覧表示する取引先レコードはApex経由で取得しています。レコードの取得方法は動的インタラクションの動作に直接的に関係はないので他の手法でも問題ないです。一覧の行がクリックされたときに処理される```handleClick```を定義しています。この処理の中でカスタムイベントを発火しています。

- 取引先詳細コンポーネント
続いて連携先となる取引先詳細コンポーネントの定義です。
```xml:accountDetailWithDI.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>55.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__AppPage">
            <property name="accountId" type="String"/>
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>
```
設定ファイルでは連携元から受けとる取引先IDを設定するパラメータとして```accountId```を定義しておきます。
```xml:accountDetailWithDI.html
<template>
    <lightning-record-view-form
        record-id={accountId}
        object-api-name="Account">
        <div class="slds-box slds-theme_default">
            <lightning-output-field field-name="Name"></lightning-output-field>
            <lightning-output-field field-name="Phone"></lightning-output-field>
            <lightning-output-field field-name="Fax"></lightning-output-field>
            <lightning-output-field field-name="Website"></lightning-output-field>
        </div>
    </lightning-record-view-form>    
</template>
```
続いて画面定義です。取引先の詳細情報を表示するため```lightning-record-view-form```を利用しています。
（各項目を表示することが目的なので他のタグでも問題ありません）
```JavaScript:accountDetailWithDI.js
import { LightningElement, wire, api } from 'lwc';
import getAccount from '@salesforce/apex/AccountListService.getAccount';
export default class AccountDetailWithDI extends LightningElement {
    @api
    accountId;
    records;
    @wire(getAccount, {id : '$accountId'})
    wiredRecords({ data, error }) {
        if (!data) return;
        this.records = data;    
    }  
}
```
続いてJavaScriptコードです。一覧と同様に取引先レコードはApexから取得しています。連携元から渡される取引先ID（ここではaccountId）をパラメータとしてApexに渡しています。

- Apexの定義
一覧・詳細で使用する取引先データを取得するSOQLを記述しています。
```java:AccountListService.cls
public with sharing class AccountListService {
    @AuraEnabled(cacheable=true)
    public static List<Account> getAccounts() {
        // ※レコードの絞り込み条件は任意で指定してください
        return [SELECT Id, Name FROM Account LIMIT 100];
    }
    @AuraEnabled(cacheable=true)
    public static List<Account> getAccount(String id) {
        return [SELECT Id, Name, Phone, Fax, Website FROM Account WHERE Id = :id];
    }
}
```


## 動的インタラクションの設定

いよいよここからが動的インタラクションの設定となります。Lightningアプリケーションビルダーから任意のページ（アプリケーションページ）を作成してください。ここに先程作成した2つのコンポーネントを配置します。ここでは左に取引先一覧コンポーネント、右に取引先詳細コンポーネントを配置しています。
※執筆時点で動的インタラクションはアプリケーションページのみのサポートになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878697/472a5c92-93e0-ab90-566c-fe7c2196a0d6.png)

取引先一覧にフォーカスを当てた上で、アプリケーションビルダー右側の設定を見ると「インタラクション」というタブが表示されます。ここには取引先一覧のコンポーネント設定ファイルで定義した```Item Selected```イベントが表示されています。「相互関係を追加」ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878697/0d6ce342-728d-636a-3725-01ac8a18c4b7.png)

動的インタラクションの設定画面が表示されるので、「コンポーネント」項目に連携先となる```accountDetailWithDI```（取引先詳細コンポーネント）を指定します。また取引先詳細コンポーネントの入力パラメータとなっている「accountId」項目に```{!Event.recordId}```（連携元で発火するイベントの取引先ID）を指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878697/5a81254e-2f7d-5bfa-e280-97617babeaa0.png)

アプリケーションビルダーを保存し終了します。これで動的インタラクションの設定は終わりです。上記設定により取引先一覧で発生したクリックイベントが取引先詳細に連携されるようになります。画面操作上も取引先一覧コンポーネントで選択した取引先の詳細が取引先詳細コンポーネントで表示されるようになると思います。

## 動的インタラクションのメリット
比較のためまずは従来型のコンポーネント間連携として一般的なLightning Message Service（LMS）を使った連携方式について考察します。LMSは特定のLightning Message Channelを介して通信をするPub/Sub型の連携方式となります。連携元となるコンポーネントでは連携情報を特定のMessage ChannelへPublish（発行）する処理を、連携先となるコンポーネントでは特定のMessageChannelをSubscribe（購読）する処理を記述する必要があります。別のMessageChannelを使うコンポーネントとの連携を新たに実装しようと思うとPub/Subのどちらも開発が必要になります（図の例でいうとComponentB2にMessageChannelAをSubscribeする処理を追加）。このようにLMSを利用するコンポーネント間連携は特定のMessageChannelが依存関係になることで、様々なコンポーネントの連携の実装において柔軟性があまり高くありませんでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878697/e41ef362-5a37-46a6-103f-d63c502c7730.png)

一方、動的インタラクションを使った連携では連携ごとの個別のMessageChannelは不要です。連携の設定情報は動的インタラクションで一元的に管理されており、連携元コンポーネントから発行されるイベントをキャッチし、指定した連携先コンポーネントにルーティングしてくれます。連携の設定は標準画面の設定ベースでできるので、新たな連携が必要になった際も簡単に連携を追加することができます。どのようなパラメータを受け渡しするかの仕様さえマッチしていれば、コーディングをすることなく新しい連携を実装することができます。連携先となるコンポーネントでは動的インタラクションに関連する実装をする必要はなく、コンポーネントの動作に必要な入力パラメータを定義しておくだけでいいので、コンポーネントの汎用性も高まりソースコードも軽量化できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878697/27072791-64de-44b1-dc79-4bb55fa98c13.png)

## 動的インタラクションがもたらすコンポーネント間連携の可能性
前述した通り動的インタラクションを使えば、コンポーネント間の連携を設定ベースで実装することができます。これが何を意味するかというと、今まではできなかった<b>標準コンポーネント同士の連携や標準コンポーネントとカスタムコンポーネント間での連携が実現できる</b>ということです。当然ですが標準コンポーネントのプログラムを改修することはできず、LMSを使うコンポーネントの連携を個別に実装することもできません。しかし動的インタラクションがあれば、プログラム改修することなく連携を実装できるので、標準コンポーネントに対しても連携を設定することができるのです。プログラムが改修できない管理パッケージのコンポーネントにおいても同様で、A社が作ったコンポーネントとB社が作ったコンポーネントを連携させるといったことも実現できるかもしれません。本稿の執筆時点で動的インタラクションに対応している標準コンポーネントは「動的アクションバーコンポーネント (パイロット)」のみですが、標準でも動的インタラクションをサポートするコンポーネントが増えてくれば、活用の幅は大きく広がることでしょう。

## まとめ
今回は新しいコンポーネント連携の実装方法として動的インタラクションを紹介しました。コンポーネントの連携が設定ベースで実装できることで、アドミンでも連携の実装をすることができ、開発と運用の効率化にも役立ちます。DevOpsを推進する上でも強力なツールとなるでしょう。次世代のコンポーネント間連携の手法としてさらなる進歩が期待できるので、引き続き今後の動向をチェックしたいと思います。

## 参考
- [Lightning アプリケーションビルダーの動的インタラクション](https://help.salesforce.com/s/articleView?id=sf.dynamic_interactions_overview.htm&type=5)