Start tracking your progress
Trailhead Home
Trailhead Home

Use Lightning Data Service and Communicate Between Components

Part 1: Use Lightning Data Service

On the Property Record page, edit the price of the property. For example, try raising it by $200,000. Click Save, and notice that the component doesn’t reload upon the price change. How can we automatically update the list of similar properties once the price on the Property Record page has been changed?

  1. In the Developer Console, open the SimilarProperties component markup.
  2. Add the following attribute.
  3. <aura:attribute name="property" type="Property__c"/>
    
  4. Add a force:recordData object to the component after the last aura:handler.
  5. <force:recordData aura:id="propertyService"
        recordId="{!v.recordId}"
        targetRecord="{!v.property}"
        recordUpdated="{!c.doInit}"
        layoutType="FULL"/>
    

    force:recordData automatically retrieves the record specified by its recordId attribute, and then stores that data in the <aura:attribute> defined by the targetRecord attribute. Then force:recordData begins to listen for changes to that record made by other components on the page, and fires the JavaScript function defined by the recordUpdated attribute when it detects a change.

  6. Save the file, and refresh the Property Record page.
  7. Edit the price of the property, and notice that the component performs a new search.
  8. Make an edit to a record in the Similar Properties list, increasing the price by more than $10,000. Notice that the component doesn’t refresh.

Part 2: Use Custom Aura Events

Next, we need to add an Aura event to let other components on the page know that we’ve updated the record. An Aura event fires an event that any other component can listen for. And, in fact, we’re now going to let the SimilarProperties component listen for the event, so that we can have multiple instances of the component on the page that all update together.

  1. In the Developer Console, click File > New > Lightning Event.
  2. Give the event a name of recordUpdated.
  3. Click Submit.
  4. Close the file.
  5. Switch to SimilarProperties.cmp.
  6. Add the following on a new line after the existing aura:handler.
  7. <aura:handler event="c:recordUpdated" action="{!c.doInit}"/>
  8. Save the file.
  9. Switch to SimilarProperty.cmp.
  10. On a new line after the aura:attribute add:
  11. <aura:registerEvent name="recordUpdated" type="c:recordUpdated"/>
  12. Save the file, then switch to SimilarPropertyController.js.
  13. Add the following to the handleSuccess function, immediately after toastEvent.fire():
  14. var recUpdate = $A.get("e.c:recordUpdated");
    recUpdate.fire();
  15. Save the file, then refresh the Property page.
  16. Make an edit to a record in the Similar Properties list.
  17. Notice that the component refreshes.

Part 3: Using Design Parameters

Finally, we’re going to make Admins love us by exposing parameters that they can change when adding the component to a page. We’re going to give them the option of specifying search criteria, as well as a range when searching by price. We’re also going to make sure that you can’t accidentally add the component to a page where it wouldn’t work. For example, we wouldn’t want to add the Similar Properties component to a Contact Record page. Using the Design file, we can specify the object types that the component can be used on. So, on the Contact Record page for example, the component won’t be shown in the available components list in Lightning App Builder.

  1. Add two new attributes to the SimilarProperties.cmp.
  2. <aura:attribute name="searchCriteria" type="String" default="Price"/>
    <aura:attribute name="priceRange" type="String" default="100000"/>
    
  3. Update the title of the lightning:card tag to {! 'Similar Properties by ' + v.searchCriteria}, so that it looks like this:
  4. <lightning:card iconName="custom:custom85" title="{! 'Similar Properties by ' + v.searchCriteria}">
  5. Switch to the SimilarPropertiesController.
  6. Update the action to var action = component.get("c.getSimilarProperties");.
  7. Update the parameters for the action to:
  8. action.setParams({
        recordId: component.get("v.recordId"),
        beds: component.get("v.property.fields.Beds__c.value"),
        price: component.get("v.property.fields.Price__c.value"),
        searchCriteria: component.get("v.searchCriteria"),
        priceRange: parseInt(component.get("v.priceRange"), 10)
    });
    
  9. In the Developer Console, click File > Open Resource and click MyPropertyController.
  10. Remove the FindProperties method and update the getSimilarProperties method to match.
  11. @AuraEnabled
    public static List<Property__c> getSimilarProperties (Id recordId, String searchCriteria, Decimal beds, Decimal price, Decimal priceRange ) {
        if (searchCriteria == 'Bedrooms') {
            return [
                SELECT Id, Name, Beds__c, Baths__c, Price__c, Broker__c, Status__c, Thumbnail__c
                FROM Property__c WHERE Id != :recordId AND Beds__c = :beds
            ];
        } else {
            Decimal range;
                if (priceRange == null) {
                    range = 100000;
                } else {
                    range = priceRange;
                }
                return [
                    SELECT Id, Name, Beds__c, Baths__c, Price__c, Broker__c, Status__c, Thumbnail__c
                    FROM Property__c WHERE Id != :recordId AND Price__c > :price - range AND Price__c < :price + range
                ];
        }
    }
    
  12. Save the files.
  13. Refresh the Property Record page and notice that we are now retrieving the properties by passing the Id, number of bedrooms, and the price of the current record.
  14. In the Developer Console, switch to the SimilarProperties.cmp file.
  15. Click the Design tile to add a design file to the component bundle.
  16. Add the following to the design file between the design:component tags.
  17. <design:attribute name="searchCriteria" label="Search By" datasource="Bedrooms, Price, Sq Foot" default="Price" description="Search for similar houses based on what criteria?"/>
    <design:attribute name="priceRange" label="Price Range" default="100000" description="When searching by Price, search using the price plus or minus this amount"/>
    

    A design attribute is the parameter that is exposed in App Builder. For each design attribute, there must be a corresponding aura attribute in the component with the exact same name as the design attribute. Design parameters that you’re exposing can be either a text input field or a picklist. Parameters that should be a picklist include a datasource attribute with a comma-separated list of options, as you can see in the <design:attribute name="searchCriteria">. We’ve also added an attribute for the priceRange. This is used by the Apex class to search for properties that are plus or minus this value.

  18. Switch to the Property Record page.
  19. Click the Setup icon and choose Edit Page.
  20. The following steps are optional:
    1. Click on the SimilarProperties component on the page, then tap the delete key (or click the X) to remove the component from the page.
    2. Click the Save button in Lightning App Builder.
    3. Switch back to the Developer Console and add the following to the Design file.
    4. <sfdc:objects>
          <sfdc:object>Property__c</sfdc:object>
      </sfdc:objects>
      

      The <sfdc:objects> list defines the type of object pages that the component can be used on. If you want to use this component on something other than a Property__c page, you simply add a new <sfdc:object> to the list.

    5. Save the file.
  21. Drag two instances of the Similar Properties component onto the page. Configure one instance for Bedrooms and one for Price.
  22. Save the page and return to the Property record.

Part 4: Add a Custom Icon, Visible in Lightning App Builder

When you create an Aura component, you have the option to create an icon for the component that appears next to the component’s name in Lightning App Builder. By default, every custom component gets a blue square with a white lightning bolt as its icon. Let’s give our component its own icon.

  1. Switch back to the Developer Console and the SimilarProperties.cmp tab, click on the SVG tile on the right-hand side of the window.
  2. (Information Only) The following steps show you how to download SVG icons from the SLDS site and include them in your component. These steps are only for your information; you don't need to perform them for this project.
    1. In a new browser tab, navigate back to the SLDS site.
    2. Navigate to the Icons section of the site.
    3. Scroll down to locate custom85 in the Custom section of icons.
    4. Normally, you would click on the Downloads link in the navigation panel, and then scroll down to the Icons section and click the Download button.
    5. After navigating to the downloaded zip file and unzipping it, open the folder and then open the custom folder.
    6. Locate the custom85.svg file and open it in a text editor.
    7. Copy the <path> tag from the SVG.
    8. In the Developer Console, switch to SimilarProperties.svg.
    9. Replace the second <path> tag with the one you just copied.
    10. At the beginning of the <path> you just pasted, add fill=”#fff” before the “d” attribute.
    11. Change width="120px" height="120px" viewBox="0 0 120 120" in the <svg> tag to:
    12. width="100px" height="100px" viewBox="0 0 100 100"

    13. Change the fill of the first <path> to #F26891.
  3. Select all and replace the .svg file in the Developer Console with:
  4. <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
        <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
            <path d="M120,108 C120,114.6 114.6,120 108,120 L12,120 C5.4,120 0,114.6 0,108 L0,12 C0,5.4 5.4,0 12,0 L108,0 C114.6,0 120,5.4 120,12 L120,108 L120,108 Z" id="Shape" fill="#2A739E"/>
            <path fill="#FFF" d="m78 24h-50v-2c0-1.1-0.9-2-2-2h-4c-1.1 0-2 0.9-2 2v56c0 1.1 0.9 2 2 2h4c1.1 0 2-0.9 2-2v-46h50c1.1 0 2-0.9 2-2v-4c0-1.1-0.9-2-2-2z m-4 14h-34c-3.3 0-6 2.7-6 6v22c0 3.3 2.7 6 6 6h34c3.3 0 6-2.7 6-6v-22c0-3.3-2.7-6-6-6z m-5.5 17h-2.5v10c0 0.6-0.4 1-1 1h-4c-0.6 0-1-0.4-1-1v-6c0-0.6-0.4-1-1-1h-4c-0.6 0-1 0.4-1 1v6c0 0.6-0.4 1-1 1h-4c-0.6 0-1-0.4-1-1v-10h-2.5c-0.5 0-0.7-0.6-0.3-0.9l11.2-10.9c0.4-0.3 0.9-0.3 1.3 0l11.2 10.9c0.3 0.3 0.1 0.9-0.4 0.9z">
            </path>
        </g>
    </svg>
    
  5. Save the file.
  6. Switch back to Lightning App Builder, and click the Refresh button at the top of the components’ list.

Part 6: Show/Hide a Spinner

You’ve done an amazing job! But as a user, it’s a little jarring when the component first loads on the page. All you initially see is the title bar of the card, and then the content comes in. Imagine if there was a network problem. The user might possibly not even realize that they should have content, or whether the newest content has loaded. Salesforce uses a Spinner to indicate when network activity is happening. Let's add a spinner to indicate when data is being retrieved or refreshed.

  1. In the Developer Console, switch to the SimilarProperties component.
  2. Add a new line at the bottom of the markup before the closing </lightning:card> and add the following.
  3. <lightning:spinner aura:id="spinner" variant="brand" size="large"/>
  4. Save the file.
  5. Add class="slds-is-relative" to the <lightning:card> tag so that it looks like this:
  6. <lightning:card iconName="custom:custom85" title="{! 'Similar Properties by ' + v.searchCriteria}" class="slds-is-relative">
    
  7. Save the file.
  8. Click STYLE to add a custom CSS file to the SimilarProperties component and replace the default contents with:
  9. .THIS {
        min-height: 13rem;
    }
    

    When using custom CSS with a Lightning component, the class .THIS is automatically applied to the HTML element wrapping the component. In this case, that is the <lightning:card>. The attribute min-height specifies the minimum height of the component. We’ve set it to the height of one property record.

  10. Save the file.
  11. Reload the Property Record page. Notice anything wrong?
  12. In the Developer Console, click CONTROLLER to open the SimilarPropertiesController.
  13. Add the following to the beginning of the doInit function before the var action declaration.
  14. var spinner = component.find("spinner");
    $A.util.removeClass(spinner, "slds-hide");
    
  15. Add the following at the end of the action’s callback.
  16. $A.util.addClass(spinner, "slds-hide");
    

    You’ve just added three lines of code to the controller. The first line var spinner = component.find("spinner"); creates a variable reference to the spinner. The component.find() method is used to find elements in the markup. When we created the <lightning:spinner>, we assigned it an aura:id of "spinner". So, we’re saying, go into the markup and find the thing that has an aura:id of spinner.


    $A.util.removeClass(spinner, "slds-hide"); instructs the framework (remember $A?) to use its utility method removeClass() to remove the CSS class of slds-hide from the spinner. The slds-hide class, as its name implies, hides any element that it’s placed on. Therefore, with the slds-hide class removed, the spinner will always be visible when the component loads. In the callback for the action, we’ve added $A.util.addClass(spinner, "slds-hide"); which simply adds the slds-hide class back onto the spinner.


  17. Save the file, and refresh the Property Record page.
  18. Modify the record and notice the spinner.

Now, the spinner appears briefly while we load data, and then disappears once the data has arrived.

Part 7: Conditionally Render Content

  1. In the Developer Console, switch back to the SimilarProperties component markup.
  2. Replace the contents of the <ul> with the following.
  3. <aura:if isTrue="{!v.similarProperties.length > 0}">
        <aura:iteration items="{!v.similarProperties}" var="item">
            <li class="slds-list__item">
                <c:SimilarProperty property="{!item}"/>
            </li>
        </aura:iteration>
        <aura:set attribute="else">
            <li class="slds-list__item">
                <h3 class="slds-text-small slds-text-color_error">No similar properties found.</h3>
            </li>
        </aura:set>
    </aura:if>
    
  4. Save the file and refresh the page.

Change the price of the Property Record to something which has no similar properties, such as $200,000, to see the empty state of the component.

Need a last-second rescue? Check your code against the following.

SimilarProperties.cmp

<aura:component controller="MyPropertyController" implements="flexipage:availableForRecordHome,force:hasRecordId" access="global">
    <aura:attribute name="recordId" type="Id"/>
    <aura:attribute name="similarProperties" type="Object[]"/>
    <aura:attribute name="property" type="Property__c"/>
    <aura:attribute name="searchCriteria" type="String" default="Price"/>
    <aura:attribute name="priceRange" type="String" default="100000"/>

    <aura:handler event="c:RecordUpdated" action="{!c.doInit}"/>
    <aura:handler name="init" value="{! this}" action="{!c.doInit}"/>

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

    <lightning:card iconName="custom:custom85" title="{! 'Similar Properties by ' + v.searchCriteria}" class="slds-is-relative">
        <div class="slds-p-left_medium slds-p-right_medium">
            <ul class="slds-list_vertical slds-has-dividers_top-space">
                <aura:if isTrue="{!v.similarProperties.length &gt; 0}">
                    <aura:iteration items="{!v.similarProperties}" var="item">
                        <li class="slds-list__item">
                            <c:SimilarProperty property="{!item}"/>
                        </li>
                    </aura:iteration>
                    <aura:set attribute="else">
                        <li class="slds-list__item">
                            <h3 class="slds-text-small slds-text-color_error">No similar properties found.</h3>
                        </li>
                    </aura:set>
                </aura:if>
            </ul>
        </div>
        <lightning:spinner aura:id="spinner" variant="brand" size="large"/>
    </lightning:card>
</aura:component>

SimilarPropertiesController.js

({
    doInit : function(component, event, helper) {
        var spinner = component.find("spinner");
        $A.util.removeClass(spinner, "slds-hide");
        var action = component.get("c.getSimilarProperties");
        action.setParams({
            recordId: component.get("v.recordId"),
            beds: component.get("v.property.fields.Beds__c.value"),
            price: component.get("v.property.fields.Price__c.value"),
            searchCriteria: component.get("v.searchCriteria"),
            priceRange: parseInt(component.get("v.priceRange"), 10)
        });
        action.setCallback(this, function(response){
            var similarProperties = response.getReturnValue();
            component.set("v.similarProperties", similarProperties);
            console.log("f: ", component.get("v.similarProperties"));
            $A.util.addClass(spinner, "slds-hide");
        });
        $A.enqueueAction(action);
    }
})

SimilarProperties.css

.THIS {
    min-height: 13rem;
}

SimilarProperties.design

<design:component>
    <sfdc:objects>
        <sfdc:object>Property__c</sfdc:object>
    </sfdc:objects>
    <design:attribute name="searchCriteria" label="Search By" datasource="Bedrooms, Price, Sq Foot" default="Price" description="Search for similar houses based on what criteria?"/>
    <design:attribute name="priceRange" label="Price Range" default="100000" description="When searching by Price, search using the price plus or minus this amount"/>
</design:component>

SimilarProperty.cmp

<aura:component>
    <aura:attribute name="property" type="Property__c"/>
    <aura:registerEvent name="recordUpdated" type="c:RecordUpdated"/>

    <lightning:recordViewForm aura:id="viewForm" recordId="{!v.property.Id}" objectApiName="Property__c">
        <div class="slds-media">
            <div class="slds-media__figure">
                <img src="{!v.property.Thumbnail__c}" class="slds-avatar_large slds-avatar_circle" alt="{!v.targetFields.Title_c}"/>
            </div>
            <div class="slds-media__body">
                <lightning:layout class="slds-hint-parent">
                    <a onclick="{!c.navToRecord}">
                        <h3 class="slds-text-heading_small slds-m-bottom_xx-small">{!v.property.Name}</h3>
                    </a>
                    <lightning:buttonIcon iconName="utility:edit" class="slds-col_bump-left" iconClass="slds-button__icon_hint" variant="bare" alternativeText="Edit Record" onclick="{!c.editRecord}"/>
                </lightning:layout>
                <lightning:layout multipleRows="true">
                    <lightning:layoutItem size="6">
                        <lightning:outputField fieldName="Beds__c"/>
                    </lightning:layoutItem>
                    <lightning:layoutItem size="6">
                        <lightning:outputField fieldName="Baths__c"/>
                    </lightning:layoutItem>
                    <lightning:layoutItem size="6">
                        <lightning:outputField fieldName="Price__c"/>
                    </lightning:layoutItem>
                    <lightning:layoutItem size="6">
                        <lightning:outputField fieldName="Status__c"/>
                    </lightning:layoutItem>
                </lightning:layout>
            </div>
        </div>
    </lightning:recordViewForm>
    <lightning:recordEditForm aura:id="editForm" recordId="{!v.property.Id}" objectApiName="Property__c" class="slds-hide" onsuccess="{!c.handleSuccess}">
        <div class="slds-media">
            <div class="slds-media__figure">
                <img src="{!v.property.Thumbnail__c}" class="slds-avatar_large slds-avatar_circle" alt="{!v.targetFields.Title_c}"/>
            </div>
            <div class="slds-media__body">
                <lightning:layout>
                    <a onclick="{!c.navToRecord}">
                        <h3 class="slds-text-heading_small slds-m-bottom_xx-small">{!v.property.Name}</h3>
                    </a>
                </lightning:layout>
                <lightning:layout multipleRows="true">
                    <lightning:layoutItem size="6">
                        <lightning:inputField fieldName="Beds__c"/>
                    </lightning:layoutItem>
                    <lightning:layoutItem size="6">
                        <lightning:inputField fieldName="Baths__c"/>
                    </lightning:layoutItem>
                    <lightning:layoutItem size="6">
                        <lightning:inputField fieldName="Price__c"/>
                    </lightning:layoutItem>
                    <lightning:layoutItem size="6">
                        <lightning:inputField fieldName="Status__c"/>
                    </lightning:layoutItem>
                </lightning:layout>
                <lightning:layout horizontalAlign="center" class="slds-m-top_large">
                    <lightning:button variant="neutral" label="Cancel" title="Cancel" type="text" onclick="{!c.handleCancel}"/>
                    <lightning:button variant="brand" label="Submit" title="Submit" type="submit"/>
                </lightning:layout>
            </div>
        </div>
    </lightning:recordEditForm>
</aura:component>

SimilarPropertyController.js

({
    navToRecord : function (component, event, helper) {
        var navEvt = $A.get("e.force:navigateToSObject");
        navEvt.setParams({
            "recordId": component.get("v.property.Id")
        });
        navEvt.fire();
    },

    editRecord : function(component, event, helper) {
        helper.showHide(component);
    },

    handleSuccess : function(component, event, helper) {
        var toastEvent = $A.get("e.force:showToast");
        toastEvent.setParams({
            "title": "Success!",
            "message": "The property's info has been updated.",
            "type": "success"
        });
        toastEvent.fire();
        var recUpdate = $A.get("e.c:RecordUpdated");
        recUpdate.fire();
        helper.showHide(component);
    },

    handleCancel : function(component, event, helper) {
        helper.showHide(component);
        event.preventDefault();
    }
})

MyPropertyController.apxc

public with sharing class MyPropertyController {
    @AuraEnabled
    public static List<Property__c> getSimilarProperties (Id recordId, String searchCriteria, Decimal beds, Decimal price, Decimal priceRange ) {
        if (searchCriteria == 'Bedrooms') {
            return [
                SELECT Id, Name, Beds__c, Baths__c, Price__c, Broker__c, Status__c, Thumbnail__c
                FROM Property__c WHERE Id != :recordId AND Beds__c = :beds
            ];
        } else {
            Decimal range;

            if (priceRange == null) {
                range = 100000;
            } else {
                range = priceRange;
            }
            return [
                SELECT Id, Name, Beds__c, Baths__c, Price__c, Broker__c, Status__c, Thumbnail__c
                FROM Property__c WHERE Id != :recordId AND Price__c > :price - range AND Price__c < :price + range
            ];
        }
    }
}