lightning:empApi Salesforce Winter19


lightning:empApi 




Hi All,
Today I will talk about brand new lightning component lightning:empApi , Which was delivered in winter 19 salesforce realease.

When to use empApi:  To see live data in on lightning component.

We had serval options to do this such as polling which is an old technique in this we call server (Apex class) again and again. Or we use streaming api, and cometD JS (external javascript) which requires lots of efforts.

Drawbacks of polling (Calling apex server) : In this we need to travel to server after every particular interval. In this sometimes our server trip is useless , if we do not have any latest data there. 

But now we have an native lightning component (lightning:empApi) , which is very helpful and reducse developer's effort.

lightning:empApi can only be used in desktop browser only and require API version 44.0 and higher. lightning:empApi can be used with platform events, PushTopic events, generic events and Change Data Capture events.

Let's see an example of lightning:empApi with platform events. My colleague Aslam bari already have written an outstanding blog (quick demo salesforce platform events) on Platform event.

In our example we will display all case which has status = open. We would not reload our component and will see live/latest data.

Demo:



So firts of all we will create a new platform event.
I have created platform event (UpdateRecord__e).

Then we need to publish it whenever a new case is inserted with status = 'open'. As well as you can also publish platform event when status is changed from open to any other or vice versa. And same you can do for delete also.

CaseTrigger:


trigger CaseTrigger on Case (after insert) { List<Case> listOfOpenCase = new List<Case>(); for(Case objCase : trigger.new){ if(objCase.Status == 'Open'){ listOfOpenCase.add(objCase); } } if(listOfOpenCase.size() > 0){ EventBus.publish(new UpdateRecord__e(message__c = listOfOpenCase[0].id)); } }


empApiExample.cmp:

<aura:component controller="empApiExampleController" implements="force:appHostable"> <aura:handler name="init" value="{!this}" action="{!c.doInit}"></aura:handler> <aura:attribute name="listOfCases" type="List"/> <aura:attribute name="subscription" type="Map" /> <aura:attribute name="showSpinner" type="Boolean" default="false"/> <lightning:empApi aura:id="empApi"/> <aura:if isTrue="{!v.showSpinner}"> <lightning:spinner size="small"/> </aura:if> <lightning:card title="empApi Example" footer=""> <div> <table class="slds-table slds-table_cell-buffer slds-table_bordered"> <thead> <tr class="slds-line-height_reset"> <th class="slds-text-title_caps" scope="col"> <div class="slds-truncate" title="Opportunity Name">Case Subject</div> </th> <th class="slds-text-title_caps" scope="col"> <div class="slds-truncate" title="Account Name">Priority</div> </th> <th class="slds-text-title_caps" scope="col"> <div class="slds-truncate" title="Close Date">Reason</div> </th> </tr> </thead> <tbody> <aura:iteration items="{!v.listOfCases}" var="objCase"> <tr class="slds-hint-parent"> <td data-label="Case Subject"> <div class="slds-truncate" title="Cloudhub">{!objCase.Subject}</div> </td> <td data-label="Case Priority"> <div class="slds-truncate" title="Cloudhub">{!objCase.Priority}</div> </td> <td data-label="Case Reason"> <div class="slds-truncate" title="Cloudhub">{!objCase.Reason}</div> </td> </tr> </aura:iteration> </tbody> </table> </div> </lightning:card> </aura:component>



empApiExampleController.js



({ doInit : function(component, event, helper) { helper.getData(component); // Get the empApi component. var empApi = component.find("empApi"); // Error handler function that prints the error to the console. var errorHandler = function (message) { console.error("Received error ", JSON.stringify(message)); }; // Register error listener and pass in the error handler function. empApi.onError(errorHandler); var channel = '/event/UpdateRecord__e'; // platform event name var replayId = -1; var callback = function (message) { console.log("Event Received : " + JSON.stringify(message)); helper.getData(component); }; // Subscribe to the channel and save the returned subscription object. empApi.subscribe(channel,replayId, callback).then(function(newSubscription) { component.set("v.subscription", newSubscription); // can be used if you want to unsubscribe event }); } })

empApiExampleHelper.js



({ getData : function(component) { component.set("v.showSpinner",true); var action = component.get('c.getListOfCases'); action.setCallback(this,function(response){ if(response.getState() === "SUCCESS"){ component.set("v.listOfCases",response.getReturnValue()); component.set("v.showSpinner",false); } }); $A.enqueueAction(action); } })


empApiExampleController (Apex)


public class empApiExampleController { @AuraEnabled public static List<Case> getListOfCases(){ return [SELECT Id,Subject,Priority,Reason FROM Case WHERE Status = 'Open']; } }

Thanks,



Lightning Data Services


Lightning Data Services

What is LDS :
                        LDS is Lightning Component that display the data on Page in Salesforce  Lightning. Using LDS we don’t need any apex  controller to perform CRUD operation. using LDS we can increase the performance. It also provide the way to cache the data to work offline in case user is not connected to network. Once the connection is restored data will sync.
LDS also handle Field Level Security and sharing rules security. Record loaded in Lightning Data Services are stored in cache and shared between all components.Using LDS all component performance is increase because data is loaded once and used by many component, no need to make separate request from different-different components . when any of the component  updates  a record then other component will  automatically updated with new value.

force:recordData :
                                    This tag is must be used to load the data in component  using LDS.
Attributes of force:recordData : 
recordId : Id of current page record that will be load
      mode : (Edit,View) its depend on what operation we are performing. If we want to update,create then Mode=’Edit’ else Mode=’View’
layoutType : specify the layout type to display record and its determine what field are included in component. 
          fields : specifies which fields in the record to query.

Target Attribute :
1.      targetRecord  : populate with loaded record.
2.      targetField      : view of loaded record.
3.     targetError      :  populate with error.    

Methods :
1.      saveRecord() : insert of update the current record in force:recordData.
2.      deleteRecord() : delete the current record in force:recordData.
3.      getNewRecord() :  load the new record instance to insert the record of that object from force:recordData.
4.      reloadRecord() : reload the current record with new data.

Keep In Mind When Using It :
i.                LDS is simple but it’s not complete replacement of apex controller.
ii.              LDS is not supported in lightning out and visualforce page. Its only for lightning.
iii.            LDS perform CRUD only on single record. It’s not supported for bulk data.
iv.             In Summer 17, release LDS used force:recordPreview component.But now it’s completely replaced by force:recordData. Difference between force:recordPreview  and force:recordData  is force:recordData  returns record in new shape using UI API and targetfield is added in parameter.
v.               To update from force:recordPreview  to force:recordData is to change reference from targetRecord to targetField.
vi.              If you want to query multiple operations in one transaction then use apex controller @AuraEnabled.


Example :
            Here we are creating a component that is added on Account detail page. It will create a new contact for current account record.


1. Create a new Record :

    Lightning Component :
    <aura:component implements="flexipage:availableForRecordHome, force:hasRecordId">
<aura:attribute name="newContact" type="Object"/>
<aura:attribute name="createContact" type="Object"/>
<aura:attribute name="newContactError" type="String"/>
<force:recordData aura:id="contactRecordCreator"
    layoutType="FULL"
    targetRecord="{!v.newContact}"
    targetFields ="{!v.createContact}"
    targetError="{!v.newContactError}"/>
<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
            <div class="Create Contact">
                        <lightning:card iconName="action:new_contact" title="Create Contact">
                                                <div class="slds-p-horizontal--small">
<lightning:input aura:id="contactField" label="First Name" value="{!v.createContact.FirstName}"/>
<lightning:input aura:id="contactField" label="Last Name" value="{!v.createContact.LastName}"/>
<lightning:button label="Save Contact" variant="brand" onclick="{!c.handleSaveContact}"/>
                                    </div>
</lightning:card>
            </div>
    <aura:if isTrue="{!not(empty(v.newContactError))}">
        <div class="recordError">
            {!v.newContactError}</div>
    </aura:if>
    </aura:component>

   Lightning Controller :
   ({
    doInit: function(component, event, helper) {
        console.log('changes');
        component.find("contactRecordCreator").getNewRecord(
            "Contact", // sObject type (entityAPIName)
            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);
                }
            })
        );
    },
    SaveContact: function(component, event, helper) {
        component.set("v.createContact.AccountId", component.get("v.recordId"));
        component.find("contactRecordCreator").saveRecord(function(saveResult) {
            if (saveResult.state === "SUCCESS" || saveResult.state === "DRAFT") {
                var resultsToast = $A.get("e.force:showToast");
                resultsToast.setParams({
                    "title": "Saved",
                    "message": "New Contact ."
                });
                resultsToast.fire();
            } else if (saveResult.state === "INCOMPLETE") {
                console.log("User is offline, device doesn't support drafts.");
            } else if (saveResult.state === "ERROR") {
                console.log(JSON.stringify(saveResult.error));
            } else {
                console.log('Unknown problem, state: ' + saveResult.state);
            }
        });
    }
    })
Result :


2. Delete A record :
            Here we added Delete Component on Account Detail Page. It will Delete Current Account when we click on Delete Button in below component.

Lightning Component :
<aura:component implements="flexipage:availableForRecordHome,force:hasRecordId">
    <aura:attribute name="recordError" type="String" access="private"/>
    <force:recordData aura:id="dataRecord"
            recordId="{!v.recordId}"
            fields="Id"
            targetError="{!v.recordError}"
            recordUpdated="{!c.handleRecordUpdated}"/>
     <div class="Delete Record">
            <lightning:card iconName="delete" title="Delete Record">
                                    <div class="slds-p-horizontal--small">
<lightning:button label="Delete Record" variant="destructive" onclick="{!c.handleDeleteRecord}"/>
                                    </div>
            </lightning:card>
       </div>
    <aura:if isTrue="{!not(empty(v.recordError))}">
        <div class="recordError"> {!v.recordError}</div>
     </aura:if>
</aura:component>

Lightning Controller :
({
handleDeleteRecord: function(component, event, helper) {
             component.find("dataRecord").deleteRecord($A.getCallback(function(deleteResult) {
            if (deleteResult.state === "SUCCESS" || deleteResult.state === "DRAFT") {
                console.log("Record is deleted.");
            } else if (deleteResult.state === "INCOMPLETE") {
                console.log("User is offline, device doesn't support drafts.");
            } else if (deleteResult.state === "ERROR") {
                console.log('Problem deleting record, error: ');
            } else {
                console.log('Unknown problem, state: ');
            }
        }));
    },
 handleRecordUpdated: function(component, event, helper) {
        var eventParams = event.getParams();
        if(eventParams.changeType === "CHANGED") {
        } else if(eventParams.changeType === "LOADED") {
        } else if(eventParams.changeType === "REMOVED") {
            var resultsToast = $A.get("e.force:showToast");
            resultsToast.setParams({
                "title": "Deleted",
                "message": "The record was deleted."
            });
            resultsToast.fire();

        } else if(eventParams.changeType === "ERROR") { }
    }
})

Result :


3. Update a Record :
            Here We create a component that will update the account detail on Account Detail page on click of save Button in component.

Lightning Component :
<aura:component implements="flexipage:availableForRecordHome,force:hasRecordId">
    <aura:attribute name="record" type="Object"/>
    <aura:attribute name="accRecord" type="Object"/>
    <aura:attribute name="recordError" type="String"/>
    <force:recordData aura:id="recordHandler"
      recordId="{!v.recordId}"
      layoutType="FULL"
      targetRecord="{!v.record}"
      targetFields="{!v.accRecord}"
      targetError="{!v.recordError}"
      mode="EDIT"
      />
    <div class="Record Details">
        <lightning:card iconName="action:edit" title="Edit Account">
            <div class="slds-p-horizontal--small">
               <lightning:input label="Account Name" value="{!v.accRecord.Name}"/>
               <br/>
               <lightning:button label="Save Account" variant="brand" onclick="{!c.SaveRecord}" />
            </div>
        </lightning:card>
    </div>
    <aura:if isTrue="{!not(empty(v.recordError))}">
        <div class="recordError">
            {!v.recordError}</div>
    </aura:if>
</aura:component>

Lightning Controller :
({
    SaveRecord: function(component, event, helper) {
        component.find("recordHandler").saveRecord($A.getCallback(function(saveResult) {
            if (saveResult.state === "SUCCESS" || saveResult.state === "DRAFT") {
                console.log("User drafts.");
            } else if (saveResult.state === "INCOMPLETE") {
                console.log("User is offline, device doesn't support drafts.");
            } else if (saveResult.state === "ERROR") {
                console.log('Problem saving record, error: ' + JSON.stringify(saveResult.error));
            } else {
                console.log('Unknown problem, state: ' + saveResult.state + ', error: ' + JSON.stringify(saveResult.error));
            }
        }));
    }
})

Result :