Work with Salesforce Data
Learning Objectives
- 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.
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.
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).
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.
Resources
- Lightning Aura Components Developer Guide: Calling a Server-Side Action
- Lightning Aura Components Developer Guide: Manage Trusted URLs
- Lightning Aura Components Developer Guide: Using External JavaScript Libraries
- Lightning Web Components Developer Guide: Work with Salesforce Data
- Lightning Web Components Developer Guide: Create a Form To Work with Records
- Lightning Web Components Developer Guide: lightning/ui*Api Wire Adapters and Functions
- Lightning Web Components Developer Guide: createRecord(recordInput)