Skip to main content

Work with Salesforce Data

Learning Objectives

After completing this unit, you’ll be able to:
  • Use base components to create forms to work with single records.
  • Use the wire service to get data.
  • Create and update data.
  • Call Apex methods.

A component without any data might look good, but it feels a little empty inside. In this unit, we fill that void and compare how Aura and Lightning components read and write Salesforce data.

If you developed Apex classes for your Aura components, you can reuse the Apex in Lightning web components. However, because of the alternatives for working with data in Lightning web components, you might find that Apex is no longer necessary.

Note

If you'd like to learn even more about working with Salesforce data, visit the Lightning Web Components and Salesforce Data module.

Create a Form to Work with Single Records

To create forms that let users view, edit, and create Salesforce records, Aura components and Lightning web components can both use base components in the lightning namespace.

These components provide a form layout and handle record CRUD changes without requiring Apex code. They use Lightning Data Service to cache and share record updates across components.

Lightning Data Service is built on top of the public User Interface API, but it only supports a subset of the API. That subset covers many of the typical use cases for working with data.

The components behave the same but have the naming-convention differences that we’ve seen earlier for Aura components and Lightning web components.

Form Function Aura component Lightning web component
Support edit, view, and read-only modes lightning:recordForm lightning-record-form
Read-only form lightning:recordViewForm lightning-record-view-form
Editable form lightning:recordEditForm lightning-record-edit-form

For most use cases, lightning-record-form provides a great starting point. It combines and simplifies the functionality of lightning-record-view-form and lightning-record-edit-form.

For more advanced use cases that require custom field layouts and custom rendering of record data, use lightning-record-view-form and lightning-record-edit-form.

Here’s an example in the BrokerDetails Aura component.

<lightning:recordForm
  objectApiName="Broker__c"
  recordId="{!v.property.Broker__c}"
  fields="{!v.brokerFields}"
  columns="2"/>

Here’s equivalent HTML in the brokerCard Lightning web component.

<lightning-record-form
  object-api-name="Broker__c" 
  record-id={brokerId}
  fields={brokerFields} 
  columns="2">
</lightning-record-form>

Custom UI to Work with Single Records

Aura components that need more control over the UI than lightning:recordForm and friends provide can use the <force:recordData> tag with their own custom UI in markup.

The PropertySummary Aura component uses <force:recordData> in its markup.

<force:recordData aura:id="service"
      recordId="{!v.recordId}"
      targetFields="{!v.property}"
      fields="['Id',
              'Thumbnail__c',
              'Address__c',
              'City__c',
              'State__c',
              'Zip__c',
              'Price__c',
              'Beds__c',
              'Baths__c',
              'Broker__r.Id',
              'Broker__r.Name',
              'Broker__r.Title__c',
              'Broker__r.Mobile_Phone__c',
              'Broker__r.Email__c',
              'Broker__r.Picture__c']" />

Instead of <force:recordData>, Lightning web components have a few techniques that use Lightning Data Service, but differ depending on whether you are reading or writing data. Consider using the wire service and JavaScript API methods only if the lightning-record-*-form components don't meet your requirements.

Use the Wire Service to Get Data

To read Salesforce data, Lightning web components use a reactive wire service, which is built on Lightning Data Service. Components use @wire in their JavaScript class to read data from one of the wire adapters in the lightning/ui*Api namespace. For the list of wire adapters that Salesforce provides, see the Resources section of this unit. You can’t write your own custom wire adapters.

We call the wire service reactive in part because it supports reactive variables, which are prefixed with $. If a reactive variable changes, the wire service provisions new data. We say “provisions” instead of “requests” or “fetches” because the wire adapter, not the component, controls when data is provided.

Here’s an example in propertySummary.js that uses @wire to retrieve a record.

import { LightningElement, api, wire } from 'lwc';
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
import NAME_FIELD from '@salesforce/schema/Property__c.Name';
import PICTURE_FIELD from '@salesforce/schema/Property__c.Picture__c';
export default class PropertySummary extends LightningElement {
    @api recordId;
    propertyName;
    pictureURL;
    @wire(getRecord, { recordId: '$recordId', fields: [NAME_FIELD, PICTURE_FIELD] })
    wiredRecord({ error, data }) {
        if (data) {
            this.propertyName = getFieldValue(data, NAME_FIELD);
            this.pictureURL = getFieldValue(data, PICTURE_FIELD);
        } else if (error) {
            // Handle error. Details in error.message.
        }
    }
}

The code imports the getRecord wire adapter from lightning/uiRecordApi.

When you use the ui*Api wire adapters, we strongly recommend importing references to objects and fields. Importing references to objects and fields ensures that your code works because Salesforce verifies that the objects and fields exist. This line of code imports a reference to the Name field in the Property custom object.

import NAME_FIELD from '@salesforce/schema/Property__c.Name';

The @api decorator makes the recordId property public. The parent component that contains propertySummary sets the value of the record-id attribute in its HTML file.

Because $recordId is prepended with a $, when its value changes, the wire service gets new data and provisions it to the component. When new data is provided, the wired function gets invoked. That process updates the properties, which in turn causes a rerender.

The wiredRecord function receives the stream of data from the wire service. The record data is returned to the data argument. Any error is returned to the error argument.

Use JavaScript API Methods to Write Data

The createRecord JavaScript API creates a record. You can also use updateRecord or deleteRecord. We’re still using Lightning Data Service under the covers so no Apex is required.

Important

Important

Don’t use @wire to create, update, or delete a record. The wire service delegates control flow to the Lightning Web Components engine. Delegating control is great for read operations, but it isn’t great for create, update, and delete operations. As a developer, you want complete control over operations that change data. That’s why you perform create, update, and delete operations with a JavaScript API instead of with the wire service.

Here’s an ldsCreateRecord Lightning web component that uses createRecord to create an account.

import { LightningElement } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { createRecord } from 'lightning/uiRecordApi';
import ACCOUNT_OBJECT from '@salesforce/schema/Account';
import NAME_FIELD from '@salesforce/schema/Account.Name';
export default class LdsCreateRecord extends LightningElement {
    accountId;
    name;
    onNameChange(event) {
        this.name = event.target.value;
    }
    createAccount() {
        const recordInput = {
            apiName: ACCOUNT_OBJECT.objectApiName,
            fields: { 
                [NAME_FIELD.fieldApiName]: this.name, 
            }
        };
        createRecord(recordInput)
            .then(account => {
                this.accountId = account.id;
                this.dispatchEvent(
                    new ShowToastEvent({
                        title: 'Success',
                        message: 'Account created',
                        variant: 'success',
                    }),
                );
            })
            .catch(error => {
                // Handle error. Details in error.message.
            });
    }
}

createRecord returns a Promise object that resolves on success with the created record. For more information on the JavaScript API, see the Resources at the end of this unit.

The HTML file of ldsCreateRecord includes a button that invokes createAccount() in the JavaScript file. The account name is set in a lightning-input component.

<template>
    <lightning-card title="LdsCreateRecord" icon-name="standard:record">
        <div class="slds-m-around_medium">
            <lightning-input label="Id" disabled value={accountId}></lightning-input>
            <lightning-input label="Name" onchange={onNameChange} class="slds-m-bottom_x-small"></lightning-input>
            <lightning-button label="Create Account" variant="brand" onclick={createAccount}></lightning-button>
        </div>
    </lightning-card>
</template>

Use Apex for Custom Data Access

We’ve looked at a few techniques that use the Lightning Data Service to work with data. These techniques usually mean less code because you don’t need any Apex. However, if you already have Apex code that you developed for Aura components, you can reuse it in Lightning web components. Alternatively, If you need a SOQL query for custom data access, you have to use an Apex method.

To access an Apex controller from an Aura component, make a call from the JavaScript controller or helper. Here’s the helper of the PropertyCarousel Aura component calling a getPictures() Apex method.

({
    loadPictures : function(component) {
        var propertyId = component.get("v.recordId");
        component.set("v.files", []);
        if (!propertyId) {
            return;
        }
        var action = component.get("c.getPictures");
        action.setParams({
            "propertyId": propertyId,
        });
        action.setCallback(this, function (response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                var files = response.getReturnValue();
                component.set("v.files", files);
            }
            else if (state === "INCOMPLETE") {
                // handle incomplete state
            }
            else if (state === "ERROR") {
                // handle error state
            }
        });
        $A.enqueueAction(action);
    }
})

Any Aura component that accesses Salesforce data has similar boilerplate. The syntax to access Apex methods is different for Lightning web components.

Lightning web components can import methods from Apex classes. The imported methods are functions that the component can call declaratively with @wire or directly in code.

Expose Apex Methods to Lightning Web Components

To expose an Apex method to a Lightning web component, the method must be static and either global or public. Annotate the method with @AuraEnabled. These requirements are the same as using an Apex method in an Aura component. The @AuraEnabled annotation means that you can call a method from an Aura component or a Lightning web component. Even though @AuraEnabled seems unexpected for a Lightning web component, using the annotation means that you can use the same Apex code in both programming models.

Use the Wire Service to Call an Apex Method

If an Apex method is cacheable (it doesn’t mutate data), you can invoke it from a component via the wire service. You must annotate the method with @AuraEnabled(cacheable=true).

Note

Don’t @wire an Apex method that creates, updates, or deletes data.

Let’s look at a MyAccountController Apex controller with a getAccounts() method.

// MyAccountController.cls
public with sharing class MyAccountController {
    @AuraEnabled(cacheable=true)
    public static List<Account> getAccounts() {
        return [SELECT Id, Name FROM Account
            WHERE AnnualRevenue > 1000000];
    }
}

This JavaScript code calls the Apex getAccounts() method using the wire service.

import { LightningElement, wire } from 'lwc';
import getAccounts from '@salesforce/apex/MyAccountController.getAccounts';
export default class HelloApexAccounts extends LightningElement {
    accounts=[];
    @wire(getAccounts, {})
    wiredAccounts({ error, data }) {
        if (error) {
            this.error = error;
        } else if (data) {
            this.accounts = data;
        }
    }
}

Note the syntax of the statement that imports getAccounts.

import getAccounts from '@salesforce/apex/MyAccountController.getAccounts';
  • MyAccountController is the name of the Apex class
  • getAccounts is the @AuraEnabled method that we’re importing

Call an Apex Method Directly

If an Apex method mutates (creates, updates, or deletes) data and therefore isn’t cacheable, you must call the method directly in code.

Here’s a Lightning web component that calls an Apex getContactList() method.

import { LightningElement } from 'lwc';
import getContactList from '@salesforce/apex/ContactController.getContactList';
export default class ApexContactList extends LightningElement {
    contacts;
    getContacts() {
        getContactList()
            .then(result => {
                this.contacts = result;
            })
            .catch(error => {
                // Handle error. Details in error.message.
            });
    }
}

The import statement looks the same as the wire service example. However, the syntax to call the method doesn’t have an @wire annotation. Instead, getContactList() returns a Promise object that resolves on success with the list of contacts.

Work with External APIs

Working with external APIs in a Lightning web component is similar to Aura components. By default, you can’t make calls to third-party APIs from JavaScript code in Lightning web components. Add a remote site as a CSP Trusted Site to allow JavaScript component code to load assets from and make API requests to that site’s domain.

Aura components and Lightning web components have the same restrictions around running JavaScript code from third-party sites. To use a third-party library, you must upload the library as a static resource.

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