1. Introduction

Lightning Data Service (LDS) is a powerful feature in the Salesforce Lightning framework that streamlines the process of working with Salesforce data in your Lightning components. It handles data caching, sharing, and data synchronization between components, allowing developers to focus on building rich user interfaces without worrying about server-side interactions.

This in-depth tutorial will guide you through the ins and outs of Lightning Data Service, providing you with the knowledge and skills to leverage it effectively in your Salesforce applications.


2. What is Lightning Data Service?

Lightning Data Service is a standardized way to create, read, update, and delete (CRUD) records in Lightning components without the need for Apex controllers. It provides a centralized data caching layer that improves performance and ensures data consistency across components.

Key Points:

  • Eliminates the need for Apex controllers for basic CRUD operations.
  • Ensures data consistency across components using the same record data.
  • Improves performance through efficient data caching.
  • Simplifies code by handling data operations declaratively.


3. Why Use Lightning Data Service?

Lightning Data Service offers several advantages over traditional data handling methods in Salesforce:

  • Simplified Development: Reduce code complexity by eliminating the need for Apex controllers and SOQL queries for basic data operations.
  • Improved Performance: Data is cached on the client-side, reducing server round-trips and improving component load times.
  • Data Consistency: Multiple components accessing the same record automatically stay in sync.
  • Error Handling: Built-in mechanisms for handling errors and data validation.

Back to Top


4. Key Features of Lightning Data Service

  • Record Caching: Efficiently caches records to minimize server calls.
  • Declarative Data Access: Access data using simple tags or decorators.
  • Automatic Data Refresh: Updates all components when a record changes.
  • Optimistic UI: Provides immediate feedback to users by updating the UI before the server confirms changes.


5. Understanding the Data Lifecycle

Before diving into code, it’s essential to understand how Lightning Data Service manages the data lifecycle:

  1. Data Fetching: When a component requests a record, LDS first checks if the data is available in the cache.
  2. Cache Management: If the data is in the cache and valid, it’s returned immediately. Otherwise, LDS fetches it from the server.
  3. Data Sharing: If multiple components request the same record, LDS ensures they share the same cached data.
  4. Data Updating: When a record is updated, LDS synchronizes changes across all components using that record.
  5. Data Deletion: LDS handles the deletion of records and updates the cache accordingly.


6. Using Lightning Data Service in Aura Components

6.1. The <force:recordData> Component

In Aura components, the <force:recordData> component is used to interact with Lightning Data Service. It provides attributes and events to perform CRUD operations.

Key Attributes:

  • recordId: The ID of the record to load, create, or update.
  • targetFields: The attribute that holds the record’s field values.
  • targetError: Holds any errors encountered during operations.
  • fields: Specifies the fields to retrieve.
  • mode: The operation mode (VIEW, EDIT, or CREATE).
  • layoutType: Determines the layout of the data (FULL, COMPACT).

Key Events:

  • recordUpdated: Fired when the record is loaded, updated, or deleted.
  • error: Fired when an error occurs during an operation.

6.2. Basic Usage Example

Let’s create a simple Aura component that displays and updates an account record.

Step 1: Create the Component

AccountComponent.cmp

<aura:component implements="flexipage:availableForAllPageTypes" access="global" >
    <!-- Attributes -->
    <aura:attribute name="recordId" type="Id" />
    <aura:attribute name="account" type="Object" />
    <aura:attribute name="errorMessage" type="String" />

    <!-- force:recordData -->
    <force:recordData
        aura:id="accountRecord"
        recordId="{!v.recordId}"
        targetFields="{!v.account}"
        targetError="{!v.errorMessage}"
        fields="Name,Industry,Phone"
        mode="EDIT"
        recordUpdated="{!c.handleRecordUpdated}" />

    <!-- Display Fields -->
    <lightning:input label="Name" value="{!v.account.Name}" />
    <lightning:input label="Industry" value="{!v.account.Industry}" />
    <lightning:input label="Phone" value="{!v.account.Phone}" />

    <!-- Save Button -->
    <lightning:button label="Save" onclick="{!c.handleSave}" />
</aura:component>

Step 2: Add Controller Logic

AccountComponentController.js

({
    handleRecordUpdated: function(component, event, helper) {
        // Handle the recordUpdated event
        var eventParams = event.getParams();
        if(eventParams.changeType === "LOADED") {
            // Record is loaded
        } else if(eventParams.changeType === "ERROR") {
            // Handle errors
            console.error('Error loading record: ' + component.get("v.errorMessage"));
        }
    },

    handleSave: function(component, event, helper) {
        component.find("accountRecord").saveRecord($A.getCallback(function(saveResult) {
            if (saveResult.state === "SUCCESS" || saveResult.state === "DRAFT") {
                // Record is saved successfully
                console.log('Record saved successfully');
            } else if (saveResult.state === "ERROR") {
                // Handle save errors
                console.error('Error saving record: ' + JSON.stringify(saveResult.error));
            }
        }));
    }
})

Explanation:

  • force:recordData: Handles data retrieval and updates.
  • targetFields: Binds the record fields to the v.account attribute.
  • handleRecordUpdated: Manages the record’s loading state and errors.
  • handleSave: Invokes the saveRecord method to persist changes.


7. Using Lightning Data Service in Lightning Web Components

In Lightning Web Components, you interact with Lightning Data Service using wire adapters and functions provided by the lightning/ui*Api modules.

7.1. @wire Adapter Methods

7.1.1. Importing Wire Adapters

  • Record Data:
  • getRecord: Retrieves a record.
  • getRecordNotifyChange: Notifies LDS of record changes.
  • Record Creation:
  • createRecord: Creates a new record.

7.1.2. Basic Usage Example

ExampleComponent.js

import { LightningElement, api, wire } from 'lwc';
import { getRecord, updateRecord } from 'lightning/uiRecordApi';

// Specify fields
const fields = ['Account.Name', 'Account.Industry', 'Account.Phone'];

export default class ExampleComponent extends LightningElement {
  @api recordId;
  account;

  @wire(getRecord, { recordId: '$recordId', fields })
  wiredRecord({ error, data }) {
    if (data) {
      this.account = data;
    } else if (error) {
      console.error('Error fetching record:', error);
    }
  }

  handleInputChange(event) {
    const field = event.target.name;
    this.account = { ...this.account, [field]: event.target.value };
  }

  handleSave() {
    const fields = {
      Id: this.recordId,
      Name: this.account.fields.Name.value,
      Industry: this.account.fields.Industry.value,
      Phone: this.account.fields.Phone.value,
    };

    const recordInput = { fields };

    updateRecord(recordInput)
      .then(() => {
        console.log('Record updated successfully');
      })
      .catch(error => {
        console.error('Error updating record:', error);
      });
  }
}

ExampleComponent.html

<template>
  <lightning-input
    label="Name"
    name="Name"
    value={account.fields.Name.value}
    onchange={handleInputChange}
  ></lightning-input>
  <lightning-input
    label="Industry"
    name="Industry"
    value={account.fields.Industry.value}
    onchange={handleInputChange}
  ></lightning-input>
  <lightning-input
    label="Phone"
    name="Phone"
    value={account.fields.Phone.value}
    onchange={handleInputChange}
  ></lightning-input>
  <lightning-button label="Save" onclick={handleSave}></lightning-button>
</template>

7.2. Imperative Apex Calls

While LDS covers most CRUD operations, sometimes you need to call Apex methods directly.

Example

import getAccounts from '@salesforce/apex/AccountController.getAccounts';

export default class AccountList extends LightningElement {
  accounts;
  error;

  connectedCallback() {
    getAccounts()
      .then((result) => {
        this.accounts = result;
      })
      .catch((error) => {
        this.error = error;
      });
  }
}


Data Caching and Consistency

Lightning Data Service automatically handles data caching:

  • Shared Cache: Components share the same cache, ensuring data consistency.
  • Automatic Updates: When a record changes, LDS notifies all components using that record.
  • Cache Invalidation: LDS invalidates the cache when data becomes stale.

Tips:

  • Avoid Redundant Calls: Use LDS to prevent unnecessary server requests.
  • Leverage Caching: Trust LDS to manage data freshness and validity.


Error Handling

Proper error handling ensures a smooth user experience.

Common Error Scenarios:

  • Validation Errors: Occur when data doesn’t meet Salesforce validation rules.
  • Server Errors: Issues with the server or network.

Handling Errors in LWC

updateRecord(recordInput)
  .then(() => {
    // Success logic
  })
  .catch(error => {
    // Error handling
    if (Array.isArray(error.body)) {
      console.error('Validation errors:', error.body);
    } else {
      console.error('Server error:', error.body.message);
    }
  });

Handling Errors in Aura

saveRecordResult = component.find("accountRecord").saveRecord();
if (saveRecordResult.state === "ERROR") {
  var errors = saveRecordResult.error;
  // Process errors
}


Best Practices

  • Use LDS When Possible: Prefer LDS over Apex for standard CRUD operations.
  • Minimize Apex Calls: Only use Apex when necessary, such as complex business logic.
  • Handle Errors Gracefully: Provide user-friendly error messages.
  • Avoid DML in Loops: Be cautious of governor limits; LDS helps mitigate this.
  • Security Considerations: Ensure field-level security and sharing rules are respected.

Limitations and Considerations

  • Custom Business Logic: LDS doesn’t replace the need for Apex when custom logic is required.
  • Unsupported Objects: Some standard and custom objects may not be fully supported.
  • Complex Queries: For SOQL queries with complex conditions, Apex is still necessary.
  • Record-Level Security: LDS respects user permissions; ensure users have the necessary access.

Lightning Data Service – Advanced Topics

Data Service Events

LDS provides events to react to data changes:

  • Data Refresh: Use getRecordNotifyChange(records) to refresh data across components.

Example

import { getRecordNotifyChange } from 'lightning/uiRecordApi';

getRecordNotifyChange([{ recordId: this.recordId }]);

Custom Record Forms

Use lightning:recordForm and lightning-record-form for rapid form development.

In Aura Components

<lightning:recordForm
    recordId="{!v.recordId}"
    objectApiName="Account"
    layoutType="Full"
    mode="Edit"/>

In Lightning Web Components

<lightning-record-form
    record-id={recordId}
    object-api-name="Account"
    layout-type="Full"
    mode="edit">
</lightning-record-form>

Conclusion

Lightning Data Service is an invaluable tool for Salesforce developers, simplifying data operations and improving application performance. By leveraging LDS, you can build more efficient, responsive, and maintainable Lightning applications.

Recap:

  • LDS handles CRUD operations without Apex.
  • Provides automatic caching and data consistency.
  • Simplifies error handling and optimizes performance.
  • Best used for standard data operations; use Apex for complex logic.

Additional Resources