LWC ( Lighting Web Component ) で @wireデコレータに紐付けた Apex の呼び出し結果をリフレッシュする方法

2023.03.10

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

サンプル

動作イメージ

サンプルの動作イメージです。 日付を指定して「検索」ボタンをクリックすると、該当する日付を「完了予定日」に持つ商談の情報をdatatableに再レンダリングします。

LWCサンプル動作イメージ

コードと解説

このサンプルは Salesforce の LWC ( Lightning Web Component ) にて、@wireデコレータに紐付けたApexの呼び出し結果をリフレッシュするサンプルになっています。

someComponent.js

import { LightningElement, wire } from 'lwc';
import { refreshApex } from '@salesforce/apex';
import getOpportunities from '@salesforce/apex/SomeController.getOpportunities';

const TODAY = new Date();

const COLUMNS = [
    { label: '取引先', fieldName: 'AccountUrl', type: 'url', typeAttributes: {label: { fieldName: 'AccountName' }, target: '_blank'}},
    { label: '商談', fieldName: 'OpportunityUrl', type: 'url', typeAttributes: {label: { fieldName: 'OpportunityName' }, target: '_blank'}}
];

export default class SomeComponent extends LightningElement {
    wireExecCounter = 0;
    error;

    /** datatableのカラム設定 */
    columns = COLUMNS;

    /** Apex実行結果を再レンダリングに対応させるために、結果を保持する変数 */
    wiredOpportunityResult;
    get isLoading() {
        return !this.wiredOpportunityResult.data && !this.wiredOpportunityResult.error;
    }

    /* 商談検索対象の日付 */
    targetDate = this.formatDate(new Date(TODAY.getFullYear(), TODAY.getMonth(), TODAY.getDay()));

    /* Apexコール結果の保持用 */
    opportunities = [];

    @wire(getOpportunities, { targetDate: '$targetDate', wireExecCounter: '$wireExecCounter' })
    async wiredOpportunity(result) {
        this.wiredOpportunityResult = result;
        const { data, error } = result;
        if ( data ) {
            this.opportunities = await Promise.all(
                data.map(async (opportunity) => {
                    const ret = {};
                    ret.Id = opportunity.Id;
                    if ( opportunity.AccountId ) {
                        ret.AccountName = opportunity.Account.Name;
                        ret.AccountUrl = `/${opportunity.AccountId}`;
                    }
                    ret.OpportunityName = opportunity.Name;
                    ret.OpportunityUrl = `/${opportunity.Id}`;

                    return ret;
                })
            );
            this.error = undefined;
        } else if ( error ) {
            this.opportunities = [];
            this.error = error;
        }
    }

    /* datatableのデータクリア */
    clearDatatable() {
        this.wiredOpportunityResult.data = null;
        this.wiredOpportunityResult.error = null;
        this.opportunities = [];
    }

    /* 検索処理 */
    search() {
        this.clearDatatable();
        this.wireExecCounter++; // wire関数の実行回数をインクリメントしてキャッシュを無効化
        this.targetDate = this.template.querySelector('[data-id="targetDate"]').value;
        refreshApex(this.wiredOpportunityResult);      
    }

    /* 日付をYYYY-MM-DDの書式で返すメソッド */
    formatDate(dt) {
        var y = dt.getFullYear();
        var m = ('00' + (dt.getMonth()+1)).slice(-2);
        var d = ('00' + dt.getDate()).slice(-2);
        return (y + '-' + m + '-' + d);
    }
}

@wireデコレータで紐付けられてコールされるApexコードは次のようにしています。

SomeController.cls

public with sharing class SomeController {
    @AuraEnabled(cacheable=true)
    public static List<Opportunity> getOpportunities(Date targetDate) {
        return [
            SELECT
                Id,
                Name,
                AccountId,
                Account.Name
            FROM
                Opportunity
            WHERE
                CloseDate = :targetDate
        ];
    }
}

テンプレートは次のようになります。

someComponent.html

<!-- sldsValidatorIgnore -->
<template>
    <lightning-card title="検索">
        <div>
            <div class="row slds-align_absolute-center">
                <lightning-input type="date" label="検索日付(完了予定日)" data-id="targetDate" value={targetDate}></lightning-input>
            </div>
            <div class="row slds-align_absolute-center" style="margin: 10px;">
                <lightning-button label="検索" onclick={search} class="slds-button"></lightning-button>
            </div>
        </div>
    </lightning-card>
    <lightning-card title="実行">
        <div class="bootstrap">
            <div if:false={isLoading}>
                <lightning-datatable
                    key-field="Id"
                    data={opportunities}
                    columns={columns}
                >
                </lightning-datatable>
            </div>
            <div if:true={isLoading} class="slds-spinner_inline spinner-padding">
                <lightning-spinner
                    variant="brand" 
                    alternative-text="Loading..."     
                    size="medium"
                >
                </lightning-spinner>
            </div>
        </div>
    </lightning-card>
</template>

ポイントはメンバ変数(JavaScriptコードの20行目)wiredOpportunityResultを用意することです。この変数は@wireデコレータで紐付けたApexメソッド(SomeController.getOpportunities)の実行結果をリフレッシュに対応させるために、Apexメソッドの実行結果を保持する変数です。

33行目の

        this.wiredOpportunityResult = result;

で結果を保持させています。

このようにメンバ変数に結果を保持させておいた上で、再レンダリングによってリフレッシュしたい際にrefreshApexメソッドにこの変数を渡すことで@wireデコレーションしたApexメソッドの再コールが行われます。サンプルでは64〜70行目のsearchメソッドにて、検索条件(targetDate)を再設定した後に

        refreshApex(this.wiredOpportunityResult);

でApexメソッドの再コールが行われ、this.opportunitiesへの商談データの詰め込みが再度行われてdatatableがリフレッシュされます。

注意点

34行目でresult.dataresult.errorを取り出しています。

        const { data, error } = result;

このdata(result.data)をメンバ変数にセットして、refreshApexメソッドに渡してもリフレッシュできそうに思ってしまいますが、できませんので注意してください。@wireデコレータで直接渡された引数(ここではresult)をそのまま丸ごとメンバ変数にセットしておく必要があります。

その他のTips

このサンプルでは検索中のLoading表示をするサンプルにもなっています。 ローディング状態を判定するisLoadingを下記のように実装しています。

    get isLoading() {
        return !this.wiredOpportunityResult.data && !this.wiredOpportunityResult.error;
    }

また、キャッシュを効かせずに確実にリフレッシュさせるために、wireExecCounterというメンバ変数を用意して、Apexメソッドのコールをするたびにカウントアップして、Apexメソッドに対して異なる引数を呼び出すようにしています。

See Also