Client-Side Caching of Apex Method Results
To improve runtime performance, annotate the Apex method with @AuraEnabled(cacheable=true)
, which caches the method results on
the client. To set cacheable=true
, a method must only get
data, it can’t mutate (change) data.
Marking a method as cacheable improves your component’s performance by quickly showing cached data from client-side storage without waiting for a server trip. If the cached data is stale, the framework retrieves the latest data from the server. Caching is especially beneficial for users on high latency, slow, or unreliable connections. Cacheable methods perform better, so use them when possible.
To use @wire
to call an Apex method, you must set
cacheable=true
.
To call an Apex method imperatively, you can choose to set cacheable=true
.
The caching refresh time is the duration in seconds before an entry is refreshed in storage. The refresh time is automatically configured in Lightning Experience and the Salesforce mobile app.
The default cache duration can change as we optimize the platform. When you design your
Lightning web components, don't assume a cache duration. If a component knows that a
cached value is invalid, for example when underlying data changes, it can query the
server for updated data and refresh the cache with refreshApex()
from @salesforce/apex
.
Refresh the Cache When Calling a Method Imperatively
To refresh stale Apex data, invoke the Apex method and then call getRecordNotifyChange(recordIds)
to
update the Lightning Data Service (LDS) cache. Lightning Data Service doesn’t manage
data provisioned by imperative Apex calls. After you invoke the Apex method, call
getRecordNotifyChange(recordIds)
to
signal to Lightning Data Service that some records are stale and refresh those
records in the
cache.
import { LightningElement, wire } from 'lwc';
import { getRecord, getRecordNotifyChange } from 'lightning/uiRecordApi';
import apexUpdateRecord from '@salesforce/apex/Controller.apexUpdateRecord';
export default class Example extends LightningElement {
@api recordId;
// Wire a record.
@wire(getRecord, { recordId: '$recordId', fields: ... })
record;
async handler() {
// Update the record via Apex.
await apexUpdateRecord(this.recordId);
// Notify LDS that you've changed the record outside its mechanisms.
getRecordNotifyChange([{recordId: this.recordId}]);
}
}
Refresh the Cache When Using @wire
To refresh Apex data provisioned via an Apex @wire
, call refreshApex()
.
The function provisions the data using the configuration bound to the @wire
and updates the cache.
refreshApex()
must be an object that was previously emitted by an
Apex @wire
.Sometimes, you know that the cache is stale. If the cache is stale, the component
needs fresh data. To query the server for updated data and refresh the cache, import
and call the refreshApex()
function. Invoke
refreshApex()
only when necessary
because it incurs a network trip to query the server.
The refreshApex()
function returns a
Promise. When the Promise is resolved, the data in the wire is fresh. The return
value from the Apex method is only available on the @wire
. The @wire
has fresh
data when the Promise resolves, but the actual value to which the Promise resolves
is meaningless. Use a then()
block to work
with the refreshed data, such as setting a property on the page.
import { refreshApex } from '@salesforce/apex';
refreshApex(valueProvisionedByApexWireService)
- Where
valueProvisionedByApexWireService
is either a property annotated with an Apex@wire
, or if you annotated a function, the argument that the wired function receives.// Example of refreshing data for a wired property // after you update data via an LDS-managed module (lightning/uiRecordApi). import { updateRecord } from 'lightning/uiRecordApi'; import { refreshApex } from '@salesforce/apex'; import getOpptyOverAmount from '@salesforce/apex/OpptyController.getOpptyOverAmount; @wire(getOpptyOverAmount, { amount: '$amount' }) opptiesOverAmount; // Update the record using updateRecord(recordInput) // Refresh Apex data that the wire service provisioned handler() { updateRecord(recordInput).then(() => { refreshApex(this.opptiesOverAmount); }); }
// Example of refreshing data for a wired property // after you update data via imperative Apex. import { refreshApex } from '@salesforce/apex'; import { getRecordNotifyChange } from 'lightning/uiRecordApi'; import getOpptyOverAmount from '@salesforce/apex/OpptyController.getOpptyOverAmount; @wire(getOpptyOverAmount, { amount: '$amount' }) opptiesOverAmount; // Update the record in Apex, such as via a button click // Refresh Apex data that the wire service provisioned handler() { updateRecordApexMethod() .then(() => { refreshApex(this.opptiesOverAmount); getRecordNotifyChange(recordIds); // Refresh the Lightning Data Service cache }); }
//Example of refreshing data for a wired function import { refreshApex } from '@salesforce/apex'; import getActivityHistory from '@salesforce/apex/ActivityController.getActivityHistory; @wire(getActivityHistory, { accountId: '$recordId', max: '500' }) wiredGetActivityHistory(value) { // Hold on to the provisioned value so we can refresh it later. this.wiredActivities = value; // Destructure the provisioned value const { data, error } = value; if (data) { ... } else if (error) { ... } ... } handler() { refreshApex(this.wiredActivities); }
Example: Refresh the Cache for a Wired Property
Let’s look at an
example. This component displays a list of opportunities over a specified amount. A
user can click to mark all the opportunities as Won. When the opportunities are
updated, the cache data becomes stale. So after the code updates the opportunities,
it calls refreshApex()
to query the server
for updated data and refresh the cache.
<!-- opportunitiesOverAmount.html -->
<template>
<!-- Display a list of opportunities -->
<p>List of opportunities over
<lightning-formatted-number value={amount} format-style="currency"></lightning-formatted-number>
</p>
<template if:true={opptiesOverAmount.data}>
<template for:each={opptiesOverAmount.data} for:item="oppty" for:index="idx">
<div key={oppty.Id} class="slds-m-bottom_medium">
<p>{idx}. {oppty.Name}</p>
<p><lightning-formatted-number value={oppty.Amount} format-style="currency"></lightning-formatted-number></p>
<p><lightning-formatted-date-time value={oppty.CloseDate}></lightning-formatted-date-time></p>
<p><lightning-badge label={oppty.StageName}></lightning-badge></p>
</div>
</template>
<!-- Click the button to change the opportunities. Requires the data to be refetched
and rerendered -->
<lightning-button label="Mark all as Closed Won" onclick={handleClick}></lightning-button>
</template>
</template>
Let’s look at the JavaScript code for the <c-opportunities-over-amount>
component. First it imports refreshApex
and the Apex methods from @salesforce/apex
.
Then it calls the Apex
method getOpptyOverAmount
with a dynamic
value, amount
. The data is rerequested
whenever this.amount
changes. The system
provides the data from the client-side cache or from the server.
// opportunitiesOverAmount.js
import { LightningElement, api, wire } from 'lwc';
import { refreshApex } from '@salesforce/apex';
import getOpptyOverAmount from '@salesforce/apex/OpptiesOverAmountApex.getOpptyOverAmount';
import updateOpptyStage from '@salesforce/apex/OpptiesOverAmountApex.updateOpptyStage';
export default class OpportunitiesOverAmount extends LightningElement {
@api amount = 500000;
@wire(getOpptyOverAmount, { amount: '$amount' })
opptiesOverAmount;
handleClick(e) {
updateOpptyStage({
amount: this.amount,
stage: 'Closed Won'
})
.then(() => {
refreshApex(this.opptiesOverAmount)
.then(() => {
// do something with the refreshed data in this.opptiesOverAmount
});
})
.catch((error) => {
this.message = 'Error received: code' + error.errorCode + ', ' +
'message ' + error.body.message;
});
}
}
The code calls refreshApex()
in the
handleClick
method. Changing the
stage
to Closed Won impacts the CloseDate
field, which means that the @wire
data is stale. The Close Date field
changes to today's date when you have a close date in the future.
To tell the
system that the data is stale, call refreshApex()
. The wire adapter marks its cache as stale, requeries
the server for updated data, updates its cache, then notifies its subscribers. When
that happens, the component's this.opptiesOverAmount
property updates, which triggers a rerender
with the new data. The close date is now July 18,
2019
(today’s date) and the opportunity stage is now “Closed
Won”.
The OpptiesOverAmountApex
class contains the getOpptyOverAmount
method, which retrieves a
list of opportunities over the specified amount. It also contains the updateOpptyStage
method, which updates the
opportunity stage for opportunities over the specified amount to “Closed Won” using
the update
DML operation.
public with sharing class OpptiesOverAmountApex {
@AuraEnabled(cacheable=true)
public static List<Opportunity> getOpptyOverAmount(Decimal amount) {
return [SELECT Id, Name, Amount, StageName, CloseDate FROM Opportunity WHERE Amount > :amount];
}
@AuraEnabled
public static void updateOpptyStage(Decimal amount, String stage) {
for (List<Opportunity> oppts:
[SELECT Id, Name, Amount, StageName, CloseDate FROM Opportunity WHERE Amount > :amount]) {
for(Opportunity o : oppts) {
o.StageName = stage;
}
update oppts;
}
return;
}
}
Example: Refresh the Cache for a Wired Function
To refresh a wired function, pass the argument the wired function receives (which is the wired
value) to refreshApex()
. In this sample code, the wired
function is wiredGetActivityHistory(value)
. Hold on to
the value provisioned by the wire service and pass it to refreshApex()
.
Call refreshApex()
imperatively using a new function.
To track the provisioned value, define a new property, wiredActivities
, and use it in wiredGetActivityHistory(value)
. Destructure the provisioned value to extract the
data
and error
objects
easily.
import { LightningElement, api, wire } from 'lwc';
import { refreshApex } from '@salesforce/apex';
import getActivityHistory from '@salesforce/apex/GalleryApexController.getActivityHistory';
export default class GalleryApexMaster extends LightningElement {
@api recordId;
wiredActivities;
@wire(getActivityHistory, { accountId: '$recordId', max: '500' })
wiredGetActivityHistory(value) {
// Hold on to the provisioned value so we can refresh it later.
this.wiredActivities = value; // track the provisioned value
const { data, error } = value; // destructure the provisioned value
if (data) { ... }
else if (error) { ... }
...
}
handleLogACall() {
// Use the value to refresh wiredGetActivityHistory().
return refreshApex(this.wiredActivities);
}
}
No comments:
Post a Comment