Lightning Data Service in Action!!

by | Sep 8, 2018 | Lightning | 3 comments

In my previous post, we had some theoretical taste of what is Lightning Data Service or LDS. In this post I will show you what LDS can do and how it can make your life as a developer better! 

I would be reusing the Student Custom Object since we are kinda used to seeing Student interactions by now. I will demonstrate the CRUD ability of LDS and that too without using an Apex controller! So lets get started!

In the video above, I have demonstrated a simple implementation of lightning data service which shows how updating a record in one component reflects it instantly in other areas of the lightning page. All using the power of LDS! Now let’s see how did I implement this.

I have used a lightning component StudentEdit_LDS for this purpose. I have dragged this component in the student detail page. Let’s have a look at the markup.



Student Registration
Now let’s dissect this markup. Let’s go through the different blocks one by one. To begin with let’s draw our attention to this particular base component.


This is what connects our component to the LDS! This force:recordData is the basis of lightning data service. Now let’s go through its individual attributes.

1. recordId: This is the record Id specifying which record we want to fetch from the LDS. No need to specify the object type. LDS is smart enough to figure it out based on just the record Id. This is not needed if you’re trying to create a new record.

2. fields: This is where you specify what fields would you like to fetch from the LDS. Now, this can be used in conjunction with another attribute layoutType. But at least one of them must be present to let the LDS know which fields you want.

3. layoutType: I haven’t used this attribute in this example because using fields was sufficient in this context. However, layoutType can also get the job done. It can have two values FULL or COMPACT . As you can guess, using a FULL layoutType retrieves the fields present in the page layout for that record. Using COMPACT would retrieve the fields present only in the COMPACT layout. Any fields not present in either of the layouts can be retrieved using fields.

4. targetRecord: This is where the LDS fills your data. Now this is in a raw format and needs a bit of formatting to be used with your input fields or output fields. If you want simple and ready to use data then that’s where targetFields comes to picture. This has to be bound to an attribute of type Object.

5. targetFields: This is also where the LDS fills your data but in a formatted and ready to use form. This is what you’re gonna use in your other markups like input fields and output fields. This too has to be bound to an attribute of type Object.

6. targetError: This is where the LDS fills the error details should you encounter an error while retrieving data or performing an operation on the record. It is bound to an attribute of type String.

7. mode: This specifies the purpose of using the LDS. It can have 2 values EDIT and VIEW. But if you’re trying to create a new record or delete an existing one then this is optional. But mind it, this is strictly implemented. If you choose VIEW mode and try to update the data it won’t let you.

8. recordUpdated: This allows you to specify a method to handle the event of the record being updated. This is pretty useful when you’re using the LDS in EDIT mode and the record gets changed in some other component. This lets you handle such a situation by letting you specify how would you react to a change in the record while you’re still editing it. Usually we reload the data.

The remaining markup is pretty straight-forward. Now let’s have a look at the JS controller file.

    handleSaveStudent: function(component, event, helper) {
        if(helper.validateStudentForm(component)) {
            console.log('Student record being saved is: '+JSON.stringify(component.get('v.student')));
            component.find("forceRecordCmp").saveRecord(function(saveResult) {
                if (saveResult.state === "SUCCESS" || saveResult.state === "DRAFT") {
                    console.log('Student saved successfully!');
                    console.log('SaveResult contents: '+JSON.stringify(saveResult));
                    console.log('Student obj details: '+JSON.stringify(component.get('v.student')));
                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: ' +
                        else {
                            console.log('Unknown problem, state: ' + saveResult.state +
                                        ', error: ' + JSON.stringify(saveResult.error));
    deleteStudent: function(cmp, event, helper){
            if (deleteResult.state === "SUCCESS" || deleteResult.state === "DRAFT") {
                console.log('Student deleted successfully!');
            else if (deleteResult.state === "INCOMPLETE") {
                console.log("User is offline, device doesn't support drafts.");
                else if (deleteResult.state === "ERROR") {
                    console.log('Problem deleting student, error: ' +
                    else {
                        console.log('Unknown problem, state: ' + deleteResult.state +
                                    ', error: ' + JSON.stringify(deleteResult.error));
    handleRecordUpdated: function(cmp, event, helper){
        var eventParams = event.getParams();
        if(eventParams.changeType === "CHANGED") {
        } else if(eventParams.changeType === "LOADED") {
            // record is loaded in the cache
        } else if(eventParams.changeType === "REMOVED") {
            //record is deleted.
        } else if(eventParams.changeType === "ERROR") {
            // there’s an error while loading, saving, or deleting the record

Let’s look at the different methods used here.


1. handleSaveStudent: This method invokes the saveRecord method on the force:recordData base component’s instance. You find this component using its aura:id and then invoke the method and pass a callback method as an argument. This call-back method fires once the saveRecord method finishes executing and a response is received from the server. It takes a parameter saveResult whose state property can give a fair idea what happened with our save operation. The state can have 4 values:

a. SUCCESS: It means everything went well. Nothing to worry about.

b. DRAFT: It means it is currently saved as a draft locally because the server wasn’t           reachable i.e, you’re offline. This is pretty handy as this offers offline support for Salesforce One mobile app. Once you’re online this will be synced back to the server.

c. INCOMPLETE: The request was sent to the server but the server couldn’t send a valid response most likely because it went down or you lost connection to the server before it could send you that response.

d. ERROR: It clearly means something went wrong. Accessing the .error property would reveal further details about what went wrong.

These 4 values are applicable for all the CRUD operations.


2. deleteStudent: This deletes the student record. The deleteRecord method is invoked in a similar fashion as the saveRecord method. It also receives an argument of a callback method. But you must be wondering, why did we do a $A.getCallback here?

This is very crucial if you’re using private attributes in your component. Passing the callback method directly could lead to access check failures with private attributes. Wrapping your callback method in $A.getCallback essentially saves you from that trouble.

Hence, as a best practice it is recommended to wrap your callback methods in all contexts of LDS inside a $A.getCallback.

3. handleRecordUpdated: As discussed earlier, this method is pretty handy when you’re  using the LDS in EDIT mode. This lets you handle the record update event and lets you decide how you’d wanna react to that. You can grab the event parameters and invoke the changeType property on it to determine what kind of change happened to your record. It also allows you to decide how to react to a specific change type. It has 4 different values as you can see in the above code segment.

a. CHANGED: When the record gets updated. Usually we reload the record when this happens. You don’t wanna update on top of a stale record.

b. LOADED: When the record gets loaded for the first time or as a reload. DONOT fire a record reload method in this block. You’ll literally enter an infinite loop leading to stack overflow.

c. REMOVED: When a record gets deleted. This is slightly tricky. As per salesforce documentation in the developer docs, any CRUD operation auto-notifies other components and almost refreshes them. You’d see the word almost in their docs. Because that’s what it is. For DELETE and UPDATE the record isn’t automatically refreshed. You need to handle it by navigating to the record home page or some other page. The detail page will show error as the record no longer exists.

d. ERROR: When there is an error when the record was getting updated/modified.


Before I finish the post, I would like to draw your attention to a very tiny method which you might have overlooked or would’ve though is of little consequence. For people who are pretty decent in javascript, it may not mean much but I would talk about it anyway.

I am talking about the helper.validateStudentForm method. Let’s take a look at it.


    validateStudentForm: function(cmp) {
        var validStudent = true;
        // Show error messages if required fields are blank
        var allValid = cmp.find('studentField').reduce(function (validFields, 
                                                                       inputCmp) {
            return validFields && inputCmp.get('v.validity').valid;
        }, true);
        return allValid;
This is a simple validation method which lets you validate all your input fields at once!

The javascript function reduce() has been used quite efficiently here. Rather than validating all the input fields one by one, we have leveraged the power of reduce to do it at once. Now dont get me wrong, the method does process all the fields one by one, but you didn’t have to write the code for them one by one. That’s my point. But in order to do that, your input fields must all have the same aura:id. That is how you will get an array of input fields having same aura:id. You can also do this by hardcoding all the elements in an array and then using reduce.

You would also notice that this is applicable only if you’re trying to enforce required fields. For custom validation logic you need to write custom logic. Also, take a look at how simple this validation has become. From the days of component.find(aura:id).set(‘v.errors’, [{message:’your custom error message’}]) to just inputCmp.showHelpMessageIfInvalid() and inputCmp.get(‘v.validity’).valid has made it simpler to validate lightning input fields. For ui namespace fields you still have to go the old way.

That’s it for now! If you’ve queries or doubts or you dont agree with something I’ve written and would like to recommend an edit, feel free to comment below or reach out to me via the contact page.


Bishwambhar Sen
Bishwambhar Sen is an IT professional with over 10 years of industry experience. He is a Salesforce certified developer and admin. When he is not configuring and customising, he loves photography, traveling and blogging.
Subscribe To Our Newsletter

Subscribe To Our Newsletter

Join our mailing list to receive the latest news and updates from our team.

You have Successfully Subscribed!

Share This