Lightning Data service
We can use Lightning Data Service to:
- load
- create
- edit
- delete
a record in your component without requiring Apex code.
- Handles sharing rules and field-level security for you.
- Improves performance and user interface consistency.
- Similar to Visualforce standard Controller
Whenever possible, use Lightning Data Service to read and modify Salesforce data in your components.
Read-only access can be entirely declarative in your component’s markup to eliminate Apex code entirely.
- Your data access code is consolidated into your component, which significantly reduces complexity.
Benefits
- Built on highly efficient local storage that’s shared across all components that use it.
- Records loaded in Lightning Data Service are cached and shared across components.
- Components accessing the same record see significant performance improvements, because a record is loaded only once, no matter how many components are using it.
- Shared records also improve user interface consistency.
- When one component updates a record, the other components using it are notified, and in most cases, refresh automatically.
Loading Record force:recordData
<!--
flexipage:availableForRecordHome:
This component can be added to:
record home page in the Lightning App Builder
(Lightning Record Page)
force:lightningQuickActionWithoutHeader:
as a custom action
(Lightning Quick Action)
force:hasRecordId:
The record ID is supplied by the implicit recordId attribute
added by the force:hasRecordId interface.
<aura:component
implements="flexipage:availableForRecordHome,force:lightningQuickActionWithoutHeader, force:hasRecordId">
<aura:attribute name="record" type="Object"/>
<aura:attribute name="simpleRecord" type="Object"/>
<aura:attribute name="recordError" type="String"/>
<!--
layoutType:
All fields on that layoutType are loaded for the record
Or provide fields attribute to specify the list of fields
to load with the fields attribute
example:
fields="Name,BillingCity,BillingState"
-------------
recordId:
The ID of the record to load
-------------
targetRecord:
targetFields:
Which component attribute to assign the loaded record
-->
<force:recordData aura:id="recordLoader"
recordId="{!v.recordId}"
layoutType="FULL"
targetRecord="{!v.record}"
targetFields="{!v.simpleRecord}"
targetError="{!v.recordError}"
recordUpdated="{!c.handleRecordUpdated}"
/>
<!-- Display a header with details about the record -->
<div class="slds-page-header" role="banner">
<p class="slds-text-heading_label">{!v.simpleRecord.Name}</p>
<h1 class="slds-page-header__title slds-m-right_small
slds-truncate slds-align-left">
{!v.simpleRecord.BillingCity}, {!v.simpleRecord.BillingState}
</h1>
</div>
<!-- Display Lightning Data Service errors, if any -->
<aura:if isTrue="{!not(empty(v.recordError))}">
<div class="recordError">
<ui:message title="Error" severity="error" closable="true">
{!v.recordError}
</ui:message>
</div>
</aura:if>
</aura:component>
Controller
({
handleRecordUpdated: function(component, event, helper) {
var eventParams = event.getParams();
if(eventParams.changeType === "LOADED") {
// record is loaded (render other component which needs record data value)
console.log("Record is loaded successfully.");
} else if(eventParams.changeType === "CHANGED") {
// record is changed
} else if(eventParams.changeType === "REMOVED") {
// record is deleted
} else if(eventParams.changeType === "ERROR") {
// there’s an error while loading, saving, or deleting the record
}
}
})
How to use this in Lightning App Builder
Component
Lightning App Builder
Saving a Record
Two cases:
- Save changes to an existing record
mode="EDIT"
and usesaveRecord
onforce.recordData
- Create and Save a new record
To save a record using Lightning Data Service:
call saveRecord
on the force:recordData
component,
and pass in a callback function to be invoked after the save operation completes.
handleSaveRecord: function(component, event, helper) {
// saveRecord
component.find("recordHandler").saveRecord($A.getCallback(function(saveResult) {
// NOTE: If you want a specific behavior(an action or UI behavior) when this action is successful
// then handle that in a callback (generic logic when record is changed should be handled in recordUpdated event handler)
if (saveResult.state === "SUCCESS" || saveResult.state === "DRAFT") {
// handle component related logic in event handler
} else if (saveResult.state === "INCOMPLETE") {
console.log("User is offline, device doesn't support drafts.");
} else if (saveResult.state === "ERROR") {
console.log('Problem saving record, error: ' + JSON.stringify(saveResult.error));
} else {
console.log('Unknown problem, state: ' + saveResult.state + ', error: ' + JSON.stringify(saveResult.error));
}
}));
}
<<aura:component implements="flexipage:availableForRecordHome,force:hasRecordId">
<aura:attribute name="record" type="Object"/>
<aura:attribute name="simpleRecord" type="Object"/>
<aura:attribute name="recordError" type="String"/>
<force:recordData aura:id="recordHandler"
recordId="{!v.recordId}"
layoutType="FULL"
targetRecord="{!v.record}"
targetFields="{!v.simpleRecord}"
targetError="{!v.recordError}"
mode="EDIT"
recordUpdated="{!c.handleRecordUpdated}"
/>
<!-- Display a header with details about the record -->
<div class="slds-page-header" role="banner">
<p class="slds-text-heading_label">Edit Record</p>
<h1 class="slds-page-header__title slds-m-right_small
slds-truncate slds-align-left">{!v.simpleRecord.Name}</h1>
</div>
<!-- Display Lightning Data Service errors, if any -->
<aura:if isTrue="{!not(empty(v.recordError))}">
<div class="recordError">
<ui:message title="Error" severity="error" closable="true">
{!v.recordError}
</ui:message>
</div>
</aura:if>
<!-- Display an editing form -->
<lightning:input aura:id="recordName" name="recordName" label="Name"
value="{!v.simpleRecord.Name}" required="true"/>
<lightning:button label="Save Record" onclick="{!c.handleSaveRecord}"
variant="brand" class="slds-m-top_medium"/>
</aura:component>
/**
* Control the component behavior here when record is changed (via any component)
*/
handleRecordUpdated: function(component, event, helper) {
var eventParams = event.getParams();
if(eventParams.changeType === "CHANGED") {
// get the fields that changed for this record
var changedFields = eventParams.changedFields;
console.log('Fields that are changed: ' + JSON.stringify(changedFields));
// record is changed, so refresh the component (or other component logic)
var resultsToast = $A.get("e.force:showToast");
resultsToast.setParams({
"title": "Saved",
"message": "The record was updated."
});
resultsToast.fire();
} else if(eventParams.changeType === "LOADED") {
// record is loaded in the cache
} else if(eventParams.changeType === "REMOVED") {
// record is deleted and removed from the cache
} else if(eventParams.changeType === "ERROR") {
// there’s an error while loading, saving or deleting the record
}
}
Creating a Record
Declare
force:recordData
without assigning a recordId.Load a record template by calling the
getNewRecord
function onforce:recordData
`getNewRecord
doesn’t return a result. It simply prepares an empty record and assigns it to the targetRecord attribute, which is used by the contact form in the component’s markup.
Apply values to the new record
Save the record by calling the
saveRecord
function onforce:recordData
.
<aura:component implements="flexipage:availableForRecordHome, force:hasRecordId">
<aura:attribute name="newContact" type="Object"/>
<aura:attribute name="simpleNewContact" type="Object"/>
<aura:attribute name="newContactError" type="String"/>
<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
<!-- note there is no recordId: recordId="{!v.recordId} here -->
<force:recordData aura:id="contactRecordCreator"
layoutType="FULL"
targetRecord="{!v.newContact}"
targetFields="{!v.simpleNewContact}"
targetError="{!v.newContactError}" />
<div class="slds-page-header" role="banner">
<p class="slds-text-heading_label">Create Contact</p>
</div>
<!-- Display Lightning Data Service errors -->
<aura:if isTrue="{!not(empty(v.newContactError))}">
<div class="recordError">
<ui:message title="Error" severity="error" closable="true">
{!v.newContactError}
</ui:message>
</div>
</aura:if>
<!-- Display the new contact form -->
<div class="slds-form_stacked">
<lightning:input aura:id="contactField" name="firstName" label="First Name"
value="{!v.simpleNewContact.FirstName}" required="true"/>
<lightning:input aura:id="contactField" name="lastname" label="Last Name"
value="{!v.simpleNewContact.LastName}" required="true"/>
<lightning:input aura:id="contactField" name="title" label="Title"
value="{!v.simpleNewContact.Title}" />
<lightning:button label="Save contact" onclick="{!c.handleSaveContact}"
variant="brand" class="slds-m-top_medium"/>
</div>
</aura:component>
({
doInit: function(component, event, helper) {
// Prepare a new record from template
component.find("contactRecordCreator").getNewRecord(
"Contact", // sObject type (objectApiName)
null, // recordTypeId
// If not specified, the default record type for the object is used,
// as defined in the user’s profile.
false, // skip cache?
// Whether to load the record template from the server instead of the client-side Lightning Data Service cache.
// Defaults to false
// callback function invoked after the empty record is created. This function receives no arguments.
/*
The callback passed to getNewRecord() must be wrapped in $A.getCallback()
to ensure correct access context when the callback is invoked.
If the callback is passed in without being wrapped in $A.getCallback(),
any attempt to access private attributes of your component results in access check failures.
*/
/*
Even if you’re not accessing private attributes,
it’s a best practice to always wrap the callback function for getNewRecord() in $A.getCallback(). Never mix (contexts), never worry.
*/
$A.getCallback(function() {
var rec = component.get("v.newContact");
var error = component.get("v.newContactError");
if(error || (rec === null)) {
console.log("Error initializing record template: " + error);
return;
}
console.log("Record template initialized: " + rec.sobjectType);
})
);
},
handleSaveContact: function(component, event, helper) {
if(helper.validateContactForm(component)) {
component.set("v.simpleNewContact.AccountId", component.get("v.recordId"));
component.find("contactRecordCreator").saveRecord(function(saveResult) {
if (saveResult.state === "SUCCESS" || saveResult.state === "DRAFT") {
// record is saved successfully
var resultsToast = $A.get("e.force:showToast");
resultsToast.setParams({
"title": "Saved",
"message": "The record was saved."
});
resultsToast.fire();
} else if (saveResult.state === "INCOMPLETE") {
// handle the incomplete state
console.log("User is offline, device doesn't support drafts.");
} else if (saveResult.state === "ERROR") {
// handle the error state
console.log('Problem saving contact, error: ' + JSON.stringify(saveResult.error));
} else {
console.log('Unknown problem, state: ' + saveResult.state + ', error: ' + JSON.stringify(saveResult.error));
}
});
}
}
})