Summer ’17 Highlight: Diving Into Lightning Data Service

One of the most exciting features in Summer ’17 is the Lightning Data Service. Through its force:recordData object, you’ll be able to build faster—with less code! In this post, we’ll break down everything there is to know, and how to leverage force:recordData in your own Lightning Component development – and there’s even a Trailhead project to help you earn a badge!

A common question developers ask when moving from Visualforce to Lightning Components is, “why do I have to write code to call an Apex controller just to save or update a record”? Happily, the first step toward answering that question appeared in the Winter ’17 release with the Developer Preview of force:recordPreview. With the Summer ’17 release, that feature has been renamed force:recordData and is officially in Beta, so it can now be used everywhere!

A quick review

If you’ve not heard of force:recordData, it’s part of the Lightning Data Service. It basically eliminates the need to write both an Apex Class and client-side JavaScript to create, read, update or delete a single record. Before we examine an actual use-case, let’s first do a quick force:recordData review. force:recordData uses the following syntax:

<force:recordData aura:id="propertyService" 
 recordId="{!v.recordId}" 
 targetRecord="{!v.property}" 
 recordUpdated="{!c.doSearch}" 
 layoutType="FULL" />

In addition to the aura:id, you’ll notice that force:recordData has a recordId attribute that it uses to retrieve the record itself. The record data is retrieved automatically by the component when the recordId is assigned, such as when the component loads with the force:hasRecordId interface.

How much of the record is returned—that is, which fields—is determined by the layoutType attribute. We can either return all of the fields defined in the standard Record Detail page layout (FULL), the fields in the compact page layout (Compact), or only the fields you choose with the fields attribute.

Once retrieved, the record result is stored in the aura:attribute defined by the targetRecord attribute. The Lightning component then begins to “listen” for that record to be updated by either the standard Record Detail layout or another component on the page.

Wait, did you catch that? Another component on the page?! By default, a component’s declaration of force:recordData is in “VIEW” mode. In other words, the attribute mode="VIEW" is the default, and is automatically applied to the force:recordData instance. However, as developers, we can explicitly set the value to mode="EDIT", meaning that the force:recordData instance is capable of modifying the record.

For example, we could have component A and component B on a page. In both components, we could declare an instance of force:recordData with the same recordId. In component A, we set the mode to “EDIT”, while component B is set to the default mode of “VIEW”. Since both components are “watching” the same recordId, if component A updates the record, component B’s instance of force:recordData will fire its own recordUpdated function.

This is all pretty sweet, and I have been happily building components with force:recordPreview for some time now. However, while building a component for the Global Lightning Now Tour’s hands-on workshop, I came up against a challenge that force:recordData was able to elegantly solve for me.

Implementing force:recordData

The hands-on workshop uses the Dreamhouse application, which is a reference application that those of us on the Developer Evangelism team built to illustrate many of the development possibilities for Lightning Experience. This reference app is for a fictitious real estate company to help them manage houses, brokers, leads, and so on.

I built a Lightning Component that can be added to a Property Record page that shows properties which are similar to the selected property based on criteria such as price, number of bedrooms, square footage, and so on. The criteria used in the search is defined as a Design Parameter, and therefore can be selected by the Admin when they add the component to the page. This also allows multiple instances of the component to be on a page, each displaying properties based on different criteria.

The initial component is straightforward. It uses a force:recordData instance, like the one above, to retrieve and monitor the record for the Property Record page being viewed. If, for example, the broker edits the price of the property on the Property Record page, the component’s force:recordData catches the change and fires its recordUpdated method, which causes the component to execute a new search for similar properties based on the new values of the Property Record. So far, so good.

Then I added the ability for the user to edit one of the similar properties being displayed, directly from the component itself, without actually navigating to its Property Record page. This was also straightforward—until I noticed a problem. If a particular property appeared in multiple instances of the component on the page, when I edited it from one of the components, its info didn’t update in the other component. Or, at least it didn’t until force:recordData came to the rescue!

Using multiple instances of force:recordData

Using force:recordData to monitor the current record on whose page the component is placed is fairly straightforward. But, we can also use force:recordData to store and monitor record data that isn’t coming from the current record page. Remember, force:recordData uses the recordId assigned to it to retrieve the record data. So we can literally assign any recordId to a force:recordData instance, not just the recordId of the current record page.

So for the Similar Properties component, I made each item in the list a component itself, named SimilarProperty, with a force:recordData instance containing the data for that particular property. This trick was the “secret sauce” to being able to update a “remote” record in the component, and having that same record update in another component without needing to reload!

force:recordData is aware of all other instances of force:recordData on the page with the same recordId. Because of this, it means that it only retrieves a record once, even if it appears in multiple components. In other words, once record data is retrieved, all of the force:recordData instances with the same recordId and the default mode=”VIEW” are “watching” the same copy of the record.

<force:recordData aura:id="propertyRecord" 
 recordId="{!v.propertyId}"
 targetFields="{!v.targetFields}"
 fields="Name, Beds__c, Baths__c, Price__c, Status__c, Thumbnail__c"
 />

In the SimilarProperty component, you’ll notice that this instance is using the fields attribute, instead of layoutType, to only retrieve the few fields that we’re displaying or are interested in within the component.

In addition, it’s using the new targetFields attribute instead of targetRecord. This is an important change, if you’ve been experimenting with force:recordPreview. During the Developer Preview, force:recordPreview only used the targetRecord attribute. With Lightning Data Service moving to Beta, the shape of the JSON response has changed. When retrieving data from the targetRecord, force:recordData uses the syntax TARGET_RECORD_NAME.fields.FIELD_NAME.value.This can seem a bit verbose, and therefore the targetFields attribute was introduced, and uses the same (and more concise) syntax that force:recordPreview used, for example {! v.TARGET_FIELDS_NAME.FIELD_NAME }.

The final use of force:recordData in the component is to pre-fill fields in a custom edit dialog, as well as handle the updating of the record when the Save button is clicked. The challenge, however, is that the edit dialog itself is also a separate component, named SimilarPropertyEdit, added to the main Similar Properties component. Which begs the question, how do you tell the edit dialog which record is being edited?

Of course, components within the same namespace can communicate via events, but since all of the components were children of the main component, I settled on simply using attributes, allowing them to “bubble up” from the child component. You’ll notice that both child components have an attribute named remoteRecordId.

<c:SimilarProperty propertyId="{!item.Id}" remoteRecordId="{!v.remoteRecordId}" showDialog="{!v.showDialog}" />
<c:SimilarPropertyEdit showDialog="{!v.showDialog}" remoteRecordId="{!v.remoteRecordId}" />

When the edit icon in the SimilarProperty component is clicked, it triggers a function named editRecord.

editRecord : function(component, event, helper) {
var recId = component.get("v.propertyId");
component.set("v.remoteRecordId", recId);
component.set("v.showDialog", "true");
}

The editRecord function simply retrieves the recordId of the record that was clicked and sets the remoteRecordId within the component. The attribute is, as mentioned, also bound to the main component’s remoteRecordId attribute, as well as to the remoteRecordId attribute in the SimilarPropertyEdit component.

Dynamically setting recordId

In the SimilarPropertyEdit component, you’ll notice that the force:recordData instance doesn’t have a recordId attribute:

<force:recordData aura:id="editRecord"
 targetRecord="{!v.selectedProperty}"
 fields="Id,Name,Beds__c,Baths__c,Price__c,Status__c"
 mode="EDIT" />

Instead, the component has a handler that is listening for a change to remoteRecordId and then fires the getRecord function:

<aura:handler name="change" value="{!v.remoteRecordId}" action="{!c.getRecord}" />

In the SimilarPropertyEditController:

getRecord : function(component) {
var tempRec = component.find("editRecord");
tempRec.set("v.recordId", component.get("v.remoteRecordId"));
tempRec.reloadRecord();
}

When the edit button in the SimilarProperty component sets the remoteRecordId, it’s propagated up to the parent component and back down into the SimilarPropertyEdit component, which in turn, triggers the change handler. The getRecord function then sets the recordId for the force:recordData instance. Since there already is an instance of force:recordData in the SimilarProperty component with this recordId, the component simply uses the data from that instance to populate its force:recordData instance.

Finally, when the Save button of the custom edit dialog is clicked, the saveRecord function is called, which updates the fields of the targetRecord and then fires the saveRecord() method of force:recordData. If the save of the data is successful, all of the force:recordData instances on the entire page—even those in different components—with that recordId are updated without needing to go to the server! In addition, the function’s callback then fires an application event to notify other components in case they need to take action due to the update of the record.

saveRecord : function(component,event,helper) {
var propBeds = parseInt(component.find('propBeds').get("v.value"), 10);
var propBaths = parseInt(component.find('propBaths').get("v.value"), 10);
var propPrice = parseInt(component.find('propPrice').get("v.value"), 10);

component.set("v.selectedProperty.fields.Beds__c.value", propBeds);
component.set("v.selectedProperty.fields.Baths__c.value", propBaths);
component.set("v.selectedProperty.fields.Price__c.value", propPrice);

var tempRec = component.find("editRecord");
tempRec.saveRecord($A.getCallback(function(result){
if (result.state === "SUCCESS" || result.state === "DRAFT") {
var event = $A.get("e.c:recordUpdated");
event.fire();
} else if (result.state === "ERROR") {
console.log('Error: ' + JSON.stringify(result.error));
} else {
console.log('Unknown problem, state: ' + result.state + ', error: ' + JSON.stringify(result.error));
}
}));
}

In conclusion, Lightning Data Service and force:recordData give Lightning Component developers a simple, straightforward way to create, read, update and delete individual records without the need to write an Apex controller. In fact, the SimilarProperties component only uses a single Apex method to execute the initial search based on the Design Parameter set by the Salesforce Admin. And yet, it reacts to changes to the main Property Record, as well as having the ability to update other records “remotely”. You can find the source code for the SimilarProperties component here, and make sure to do the whole project to earn a badge!

Be sure to check out the Release Notes for Summer ’17 for even more developer goodness, as well as the full Lightning Components Developer’s Guide.

Leave your comments...

Summer ’17 Highlight: Diving Into Lightning Data Service