Skip to main content

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 the ContactController 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 the ContactController 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 the getContactsBornAfter Apex method imperatively, passing the birthDate 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 the catch 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.

  1. Create an Apex class named AccountController:
    1. In the Explorer pane, right-click the classes folder and select SFDX: Create Apex Class.
    2. For the class name, enter AccountController and press Enter.
    3. Press Enter again to accept the default directory.
  2. Replace the contents of your AccountController class with this code:
    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
            ];
        }
    }
    Code highlights:
    • 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.
  3. Create a Lightning web component named accountList.
  4. Replace the contents of your accountList.js file with this code:
    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;
    }

    Code highlights:
    • 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 the getAccounts function to retrieve the data.
    • Line 14: We store the result in the accounts property. If the operation succeeds, the records are accessible on accounts.data. If it fails, the error surfaces in accounts.error.
  5. Replace the contents of your accountList.html file with this code:
    <template>
        <lightning-card>
            <template if:true={accounts.data}>
                <lightning-datatable
                    key-field="Id"
                    data={accounts.data}
                    columns={columns}
                >
               </lightning-datatable>
            </template>
        </lightning-card>
    </template>
    Code highlights:
    • Lines 4–9: We define the lightning-datatable base component to use accounts.data and columns that are populated in the JavaScript file.
  6. Save your AccountController class.
  7. 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>
  8. Save all three component files.
  9. Deploy the force-app/main/default folder to your Trailhead Playground.
  10. In your Trailhead Playground, navigate to the Lightning App Builder and open the Working with Data page.
  11. Drag the accountList component to the main region of the page.
  12. Save the page.
  13. Return to the Working with Data page to see your new component.
Note

Some things to consider when using the lightning-datatable component.

  • Some data types are not supported in the lightning-datatable component today. However, you can use custom types. To learn more, scroll to Create Custom Data Types in the documentation.
  • The lightning-datatable component doesn’t work on mobile today. If you need to support mobile, create a custom table instead.

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

Keep learning for
free!
Sign up for an account to continue.
What’s in it for you?
  • Get personalized recommendations for your career goals
  • Practice your skills with hands-on challenges and quizzes
  • Track and share your progress with employers
  • Connect to mentorship and career opportunities