Use Apex to Work with Data
Learning Objectives
After completing this unit, you’ll be able to:
- Recognize when you need to use Apex to work with Salesforce data.
- Call Apex in two different ways.
- Work with lists of records using Apex and lightning-datatable.
Apex in Lightning Web Components
We’ve covered the benefits of Lightning Data Service and how to use it, but sometimes, neither the lightning-record-*-form
components nor the LDS wire adapters and functions suit a particular use case. For example, when you want to customize a single-record data transaction, or perform multi-record operations on a single transaction, Apex is the best choice.
Use Apex Methods with Lightning Web Components
An Apex method used in a Lightning web component must be static
, public
or global
, and annotated with @AuraEnabled
immediately before the method definition. The @AuraEnabled
annotation makes the Apex method available to Lightning components (both Lightning web components and Aura components).
Allowing the framework to cache data eliminates repeated server calls making future read operations run faster. We mark a method as cacheable by setting cacheable = true
in the @AuraEnabled
annotation. When an @AuraEnabled
method is cacheable, Data Manipulation Language (DML) operations are not allowed. In line 2 of this example, we make the getContactsBornAfter
method cacheable.
ContactController.cls
public with sharing class ContactController { @AuraEnabled(cacheable=true) public static List<Contact> getContactsBornAfter(Date birthDate) { return [ SELECT Name, Title, Email, Phone FROM Contact WHERE Birthdate > :birthDate WITH SECURITY_ENFORCED ]; } }
When a method is cacheable, newly added or changed versions of records may not be returned until the cache is refreshed. Learn how to manually refresh the cache in the next section.
Call Apex Methods from LWC
There are two ways to interact with Apex methods from Lightning web components: either wire the method or call the method imperatively. Let’s consider both approaches.
Call Apex Using @wire
To wire an Apex method, the method must be cacheable. To wire a cacheable Apex method, use the @wire
decorator (the same way you use an LDS wire adapter). Calling Apex this way delegates control to the Lightning Web Components engine and creates a reactive service. Every time the value of a parameter that’s passed to the Apex method changes, the Apex method runs, provisioning the decorated property or function with the new value. Because wired methods must be cacheable, data can come from the LDS cache or the server. To refresh the data that was cached by an Apex method, call the refreshApex function.
Note: Lightning Data Service is unaware of the data that's cached by Apex methods. When an LDS function updates a record, that update has no impact on data that was cached by an Apex method.
Here’s an example of using @wire
to call Apex. This code gets contacts who were born after the specified birth date.
wireApexProperty.js
import { LightningElement, api, wire } from 'lwc'; import getContactsBornAfter from '@salesforce/apex/ContactController.getContactsBornAfter'; export default class WireApexProperty extends LightningElement { @api minBirthDate; @wire(getContactsBornAfter, { birthDate: '$minBirthDate' }) contacts; }
Code highlights:
- Line 2: We import the
getContactsBornAfter
function from theContactController
Apex class. This points to the corresponding Apex method. - Line 4: We define an
@api minBirthDate
property. When you use this component in your code or expose a FlexiPage attribute, you can pass a date to the@api minBirthDate
property. - Line 5: The
@wire
decorator receives two parameters: the Apex method that we want to call (getContactsBornAfter
), and the parameter that the adapter needs (birthDate
). We pass$minBirthDate
as a reactive variable (starting with$
). - Line 6: The result is stored in the
contacts
property. - Lines 5–6: Initially, the Apex method provisions data to the
contacts
property, and stores that data in the LDS cache. Because$minBirthDate
is reactive, every time its value changes, the Apex method runs and provisions new data, from either the cache or the server.
Call Apex Imperatively
An alternative to calling Apex with @wire
is to call Apex imperatively. Call Apex imperatively when you need to control the invocation of read operations, and when you modify records. To call Apex imperatively, invoke the imported function from the component’s JavaScript file. The function returns a JavaScript promise (as you do when you call an LDS function imperatively).
You can call both cacheable and non-cacheable Apex methods imperatively. However, you cannot refresh a cacheable Apex method imperatively. Instead, call the method using @wire
and refresh it with refreshApex
.
In the callApexImperative.js example, when a user clicks a lightning-button
in the .html file (not shown), handleButtonClick
calls the getContactsBornAfter
Apex method imperatively.
callApexImperative.js
import { LightningElement, api, wire } from 'lwc'; import getContactsBornAfter from '@salesforce/apex/ContactController.getContactsBornAfter'; export default class CallApexImperative extends LightningElement { @api minBirthDate; handleButtonClick() { getContactsBornAfter({ //imperative Apex call birthDate: this.minBirthDate }) .then(contacts => { //code to execute if related contacts are returned successfully }) .catch(error => { //code to execute if related contacts are not returned successfully }); } }
Code highlights:
- Line 2: We import the
getContactsBornAfter
function from theContactController
class. - Line 4: We define a public
minBirthDate
property, to which you can pass a date when using this component in your code or exposing a FlexiPage attribute. - Lines 6-7: When the
handleButtonClick
method is invoked by the framework, we invoke thegetContactsBornAfter
Apex method imperatively, passing thebirthDate
that the method needs to get contacts born after the specified birth date. - Lines 9–14: The imperative Apex call in line 6 returned a promise. If the Apex method call is successful, the promise is fulfilled and the
then
method runs. Otherwise, the promise is rejected and thecatch
method runs.
The preferred way to work with lists of records in Lightning web components is to use the lightning-datatable
base component. Use lightning-datatable
to create tables of data with features such as infinite scrolling, inline editing, header and row-level actions, resizing, and more. This UI component needs to be fed data. The most common way to generate that data is to call Apex in any of the ways explained earlier in this module.
Deploy a Lightning Web Component That Lists Records in a Table
Let’s work with an example that displays a list of existing Accounts in a lightning-datatable
. We’ll use Apex and @wire
to retrieve the records.
- Create an Apex class named
AccountController
:
- In the Explorer pane, right-click the classes folder and select SFDX: Create Apex Class.
- For the class name, enter
AccountController
and press Enter. - Press Enter again to accept the default directory.
- Replace the contents of your AccountController class with this code:Code highlights:
public with sharing class AccountController { @AuraEnabled(cacheable=true) public static List<Account> getAccounts() { return [ SELECT Name, AnnualRevenue, Industry FROM Account WITH SECURITY_ENFORCED ORDER BY Name ]; } }
- Line 2: We annotate the method with
@AuraEnabled(cacheable=true)
so that the results are cached. - Line 3: We define the
getAccounts
method in Apex, to perform a read operation and retrieve existing accounts.
- Create a Lightning web component named
accountList
. - Replace the contents of your accountList.js file with this code:Code highlights:
import { LightningElement, wire } from 'lwc'; import NAME_FIELD from '@salesforce/schema/Account.Name'; import REVENUE_FIELD from '@salesforce/schema/Account.AnnualRevenue'; import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry'; import getAccounts from '@salesforce/apex/AccountController.getAccounts'; const COLUMNS = [ { label: 'Account Name', fieldName: NAME_FIELD.fieldApiName, type: 'text' }, { label: 'Annual Revenue', fieldName: REVENUE_FIELD.fieldApiName, type: 'currency' }, { label: 'Industry', fieldName: INDUSTRY_FIELD.fieldApiName, type: 'text' } ]; export default class AccountList extends LightningElement { columns = COLUMNS; @wire(getAccounts) accounts; }
- Lines 2–4: We import field references, same as in the previous examples.
- Line 5: We import the
getAccounts
function from the AccountController class. - Line 13: We use
@wire
with thegetAccounts
function to retrieve the data. - Line 14: We store the result in the
accounts
property. If the operation succeeds, the records are accessible onaccounts.data
. If it fails, the error surfaces inaccounts.error
.
- Replace the contents of your accountList.html file with this code:Code highlights:
<template> <lightning-card> <template if:true={accounts.data}> <lightning-datatable key-field="Id" data={accounts.data} columns={columns} > </lightning-datatable> </template> </lightning-card> </template>
- Lines 4–9: We define the
lightning-datatable
base component to useaccounts.data
andcolumns
that are populated in the JavaScript file.
- Save your AccountController class.
- Replace the contents of your accountList.js-meta.xml with this code so that the component is available on app pages:
<?xml version="1.0" encoding="UTF-8"?> <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata"> <apiVersion>48.0</apiVersion> <isExposed>true</isExposed> <targets> <target>lightning__AppPage</target> </targets> </LightningComponentBundle>
- Save all three component files.
- Deploy the force-app/main/default folder to your Trailhead Playground.
- In your Trailhead Playground, navigate to the Lightning App Builder and open the Working with Data page.
- Drag the accountList component to the main region of the page.
- Save the page.
- Return to the Working with Data page to see your new component.
Now you know a few ways to interact with Salesforce data in your Lightning web components. Next, you learn how to handle server errors when they occur.
Resources