Overview
One of the big changes developers face when moving from the Visualforce world into Lightning components is how to communicate with the server. Up until the Summer ’17 release, this meant creating an Apex class to do anything—even simple CRUD interactions that were handled “automagically” in Visualforce. With the Summer ’17 release and the introduction of Lightning Data Service, this all changed. So, for our component, we use Lightning Data Service to create a new property record—without writing Apex!
Goals
Perform SOQL and CRUD operation without help of APEX controller.
Description
Tag :- force:recordData
Implements :- flexipage:availableForRecordHome, force:hasRecordId
Attributes :-
- aura:id : to identify the tag uniquely in js controller.
- recordId : to query the record we could pass the value over here
- layoutType : if we do not want to provide fields we could always use layout. It will identify the layout assigned to user profile and get the field values. Then it query all the fileds in layout. For layout there are two values:-
- COMPACT : The compact layout (popup layout) assigned to user.
- FULL : Actual record layout assigned to user.
- targetRecord : Component attribute in which we want to load the data. In other words lightning component variable.
- targetFields : Fields list to query (component object).
- fields : Fields list(comma separated) to query (hard coded).
- targetError : To show and handle error msgs
- recordUpdated : JS controller event to check the below events:
- LOADED : handle record load (SOQL)
- CHANGED : handle record change (onChange of data fields value)
- REMOVED : handle record removal (on delete the record)
- ERROR : handle error(We should handle this event first.)
- mode : EDIT to perform DML on record and VIEW to show record on UI.
Example :-
[code language=”html”]<force:recordData aura:id=”idToIdentifyTagUniquely”
recordId=”{!v.recordId}”
layoutType=”FULL”
targetRecord=”{!v.sObject_Attribut_Variable_to_hold_record}”
targetFields=”{!v.object_Attribut_Variable_contains_fields}”
targetError=”{!v.errorVariable}”
fields=”Id,Name,CreatedDate”
recordUpdated=”{!c.jsControllerMethod}”
/>[/code]
Actions
Creating a Record
To create a record using Lightning Data Service, declare force:recordData without assigning a recordId attribute value. If we also want to save the record we have to set the mode attribute value as EDIT.
- Events : Aura has defined below events:
- getNewRecord : This event has been defined to create the variable in Component and init its value. It accepts below params.
- objectApiName : sObject API Name
- recordTypeId : recordTypeId if not applicable then “null”
- skipCache : true/false as required.
- callback : JS method to maintain callback.
- saveRecord : We will discuss this under save record section.
- getNewRecord : This event has been defined to create the variable in Component and init its value. It accepts below params.
Loading a Record (in APEX language SOQL the record using Id)
To load a record in component variable we just need to specify the recordId attribute’s value. Along with id we could use targetFields attribute or layoutType attribute to specify the name of fields we want to SOQL.
Saving a Record (in APEX language UPSERT)
To save the record we have to load the recordData component in JS controller using aura Id and using that object we call saveRecord method.
SaveRecord method would be taking only callback method as parameter. In that method we would be having one parameter as saveResult, using the variable we could identify below states:
- SUCCESS : If the Upsert happened successfully.
- DRAFT : Its same as success.
- INCOMPLETE : User is offline, device doesn’t support drafts.
- ERROR : Problem saving record
- If no state is there we should handle that also.
saveResult object will contain below information:
- objectApiName : String – The object API name for the record.
- entityLabel : String – The label for the name of the sObject of the record.
- error : String – Error is one of the following:
- A localized message indicating what went wrong.
- An array of errors, including a localized message indicating what went wrong. It might also include further data to help handle the error, such as field- or page-level errors.
- error is undefined if the save state is SUCCESS or DRAFT.
- recordId : String – The 18-character ID of the record affected.
- state : String – The result state of the operation. We have already discussed the state values
Deleting a Record (in APEX language Delete)
To save the record we have to load the recordData component in JS controller using aura Id and using that object we call deleteRecord method.
DeleteRecord method would be taking only callback method as parameter. In that method we would be having one parameter as deleteResult, using the variable we could identify below states:
- SUCCESS : If the Delete happened successfully.
- DRAFT : Its same as success.
- INCOMPLETE : User is offline, device doesn’t support drafts.
- ERROR : Problem deleting record
- If no state is there we should handle that also.
Change Event Handler
To identify the changes made through data service we use change event handler.
For this we have to create one JS controller function and specify the function under recordUpdated attribute in force:recordData tag.
We have already discussed the events we need to handle in the Description section.
Example:
The UI :
The Lightning Page: ldsQuickContact.cmp
[code language=”html”]<aura:component implements=”force:lightningQuickActionWithoutHeader,force:hasRecordId”></pre>
<aura:attribute name=”account” type=”Object”/>
<aura:attribute name=”simpleAccount” type=”Object”/>
<aura:attribute name=”accountError” type=”String”/>
<force:recordData aura:id=”accountRecordLoader”
recordId=”{!v.recordId}”
fields=”Name,BillingCity,BillingState”
targetRecord=”{!v.account}”
targetFields=”{!v.simpleAccount}”
targetError=”{!v.accountError}”
/>
<aura:attribute name=”newContact” type=”Object” access=”private”/>
<aura:attribute name=”simpleNewContact” type=”Object” access=”private”/>
<aura:attribute name=”newContactError” type=”String” access=”private”/>
<force:recordData aura:id=”contactRecordCreator”
layoutType=”FULL”
targetRecord=”{!v.newContact}”
targetFields=”{!v.simpleNewContact}”
targetError=”{!v.newContactError}”
/>
<aura:handler name=”init” value=”{!this}” action=”{!c.doInit}”/>
<!– Display a header with details about the account –>
<div class=”slds-page-header” role=”banner”>
<p class=”slds-text-heading_label”>{!v.simpleAccount.Name}</p>
<h1 class=”slds-page-header__title slds-m-right_small
slds-truncate slds-align-left”>Create New Contact</h1>
</div>
<!– Display Lightning Data Service errors, if any –>
<aura:if isTrue=”{!not(empty(v.accountError))}”>
<div class=”recordError”>
<ui:message title=”Error” severity=”error” closable=”true”>
{!v.accountError}
</ui:message>
</div>
</aura:if>
<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 –>
<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:input aura:id=”contactField” type=”phone” name=”phone” label=”Phone Number”
pattern=”^(1?(-?d{3})-?)?(d{3})(-?d{4})$”
messageWhenPatternMismatch=”The phone number must contain 7, 10, or 11 digits. Hyphens are optional.”
value=”{!v.simpleNewContact.Phone}” required=”true”/>
<lightning:input aura:id=”contactField” type=”email” name=”email” label=”Email”
value=”{!v.simpleNewContact.Email}” />
<lightning:button label=”Cancel” onclick=”{!c.handleCancel}” class=”slds-m-top_medium” />
<lightning:button label=”Save Contact” onclick=”{!c.handleSaveContact}”
variant=”brand” class=”slds-m-top_medium”/>
</aura:component>[/code]
JS Controller : ldsQuickContactController.js
[sourcecode language=”javascript”]
({
doInit: function(component, event, helper) {
component.find(“contactRecordCreator”).getNewRecord(
“Contact”, // objectApiName
null, // recordTypeId
false, // skip cache?
$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);
}
else {
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”) {
// Success! Prepare a toast UI message
var resultsToast = $A.get(“e.force:showToast”);
resultsToast.setParams({
“title”: “Contact Saved”,
“message”: “The new contact was created.”
});
// Update the UI: close panel, show toast, refresh account page
$A.get(“e.force:closeQuickAction”).fire();
resultsToast.fire();
// Reload the view so components not using force:recordData
// are updated
$A.get(“e.force:refreshView”).fire();
}
else if (saveResult.state === “INCOMPLETE”) {
console.log(“User is offline, device doesn’t support drafts.”);
}
else if (saveResult.state === “ERROR”) {
console.log(‘Problem saving contact, error: ‘ +
JSON.stringify(saveResult.error));
}
else {
console.log(‘Unknown problem, state: ‘ + saveResult.state +
‘, error: ‘ + JSON.stringify(saveResult.error));
}
});
}
},
handleCancel: function(component, event, helper) {
$A.get(“e.force:closeQuickAction”).fire();
},
})
[/sourcecode]
JS Helper : ldsQuickContactHelper.js
[sourcecode language=”javascript”]
({
validateContactForm: function(component) {
var validContact = true;
// Show error messages if required fields are blank
var allValid = component.find(‘contactField’).reduce(function (validFields, inputCmp) {
inputCmp.showHelpMessageIfInvalid();
return validFields inputCmp.get(‘v.validity’).valid;
}, true);
if (allValid) {
// Verify we have an account to attach it to
var account = component.get(“v.account”);
if($A.util.isEmpty(account)) {
validContact = false;
console.log(“Quick action context doesn’t have a valid account.”);
}
return(validContact);
}
}
})
[/sourcecode]
Example use :
We could use above component as a quick contact creator in Account detail page:
Considerations
Lightning Data Service is powerful and simple to use. However, it’s not a complete replacement for writing your own data access code. Here are some considerations to keep in mind when using it.
Lightning Data Service is only available in Lightning Experience and the Salesforce app. Using Lightning Data Service in other containers, such as Lightning Components for Visualforce, Lightning Out, or Communities isn’t supported. This is true even if these containers are accessed inside Lightning Experience or the Salesforce mobile app, for example, a Visualforce page added to Lightning Experience.
Lightning Data Service supports primitive DML operations—create, read, update, and delete. It operates on one record at a time, which you retrieve or modify using the record ID. Lightning Data Service supports spanned fields with a maximum depth of five levels. Support for working with collections of records or for querying for a record by anything other than the record ID isn’t available. If you must support higher-level operations or multiple operations in one transaction, use standard @AuraEnabled Apex methods.
Lightning Data Service shared data storage provides notifications to all components that use a record whenever a component changes that record. It doesn’t notify components if that record is changed on the server, for example, if someone else modifies it. Records changed on the server aren’t updated locally until they’re reloaded. Lightning Data Service notifies listeners about data changes only if the changed fields are the same as in the listener’s fields or layout.