Performance Tips

Storable Actions : Using action.setStorable();

  • Marks the actions as storable to quickly show cached data from client-side storage without waiting for a server trip.

  • refreshAge: 30 secs

  • expirationAge: 900 secs = 15 min.


var action = component.get("c.svrGetAccounts");

action.setStorable(); // here the magic happens # Step:6 in the animation below 
// takes in opt parameter: ignoreExisting - default value = false
// set it true when you need to bypass the cache
//    useful: when you know that cached-data is invalid you wnat to ignore it, 
//        so you do not use it on normal cases

action.setCallback(this, function(response) { // callback may be called twice as in sceneario:3 as shown below
    // use the response
});

$A.enqueueAction(action);

action.setStorable-1

action.setStorable-2

action.setStorable-2

Notes

  1. A storable action might result in no call to the server. Never mark as storable an action that updates or deletes data.. Use it for non-mutable data (read-only). Since most server requests are read-only and idempotent, storable action will provide performance boost in these cases.

  2. For storable actions in the cache, the framework returns the cached response immediately and also refreshes the data if it’s stale. Therefore, storable actions might have their callbacks invoked twice:

    • first with cached data
    • with updated data from the server when when age >= refreshAge (30 secs)
  3. Keys used in determining an identical action:

    • Apex controller name
    • Method name (@AuraEnabled)
    • Method parameter values (method signature and parameter values)

Lightning Data Services (LDS): force:recordData

Provides well-connected Components

  • Component A asks LDS: [Contact.id, Contact.Name, Contact.Phone]

    • LDS gets that data from the Server and Caches it
  • Component B asks LDS: [Contact.id, Contact.Name, Contact.Phone]

    • LDS gets that data from the Cache
  • Component C asks LDS: [Contact.id, Contact.Name, Contact.Phone, Contact.MobilePhone]
    • LDS gets that data from the Cache and fetches the delta - extra field Contact.MobilePhone and Caches it

action.setStorable-1

Component A Markup


<aura:component implements="force:hasRecordId, flexipage:availableForAllPageTypes" access="global">
    <aura:attribute name="recordId" type="Id" />
    <aura:attribute name="contact"  type="MyContact__c" />

    <force:recordData aura:id="dataService"
            recordId="{!v.recordId}"
            targetFields="{!v.contact}"
            fields="['Id, 'Name', 'Phone__c']"

    />

</aura:component>

Component B Markup

<aura:component implements="force:hasRecordId, flexipage:availableForAllPageTypes" access="global">
    <aura:attribute name="recordId" type="Id" />
    <aura:attribute name="contact"  type="MyContact__c" />

    <force:recordData aura:id="dataService"
            recordId="{!v.recordId}"
            targetFields="{!v.contact}"
            fields="['Id, 'Name', 'Phone__c']"

    />

    <!-- other stuff -->
</aura:component>

Component C Markup

<aura:component implements="force:hasRecordId, flexipage:availableForAllPageTypes" access="global">
    <aura:attribute name="recordId" type="Id" />
    <aura:attribute name="contact"  type="MyContact__c" />

    <force:recordData aura:id="dataService"
            recordId="{!v.recordId}"
            targetFields="{!v.contact}"
            fields="['Id, 'Name', 'Phone__c', 'Mobile_Phone__c']"

    />

    <!-- other stuff -->
</aura:component>

Data Binding

Component with Bound Expression - 2-way data-binding

<aura:component controller="ContactCtrl" implements="force:hasRecordId, flexipage:availableForAllPageTypes" 
                access="global">
    <aura:attribute name="contacts" type="Contact[]" />

    <aura:iteration items="{!v.contacts}" var="contact">
        <!--  Note: {!contact.firstName} : this is 2-way data-binding to var contact-->
        <lightning:input value="{!contact.firstName}" />
        <!--
          - if user changes this value in the input element, the model (var contact) changes automatically 

          - At framework level we need to setup 2 event-listeners - this is in a loop here, 
            so 2 event-listeners per iterations...

          - this is expensive!

            // handle Model to View change
            1. contact.addEventListener("change", function(event) {
                 // put into in VIEW - input element
                 inputElement.value = contact.firstName;
               }
            // handle View to Model change 
            2. inputElment.addEventListener("change", function(event) {
                 // update the model from value from the input element
                 contact.firstName = inputElement.value;
               }

        -->
    </aura:iteration>

</aura:component>

Component with unBound Expression - 1-TIME data-binding

<aura:component controller="ContactCtrl" implements="force:hasRecordId, flexipage:availableForAllPageTypes" 
                access="global">
    <aura:attribute name="contacts" type="Contact[]" />

    <aura:iteration items="{!v.contacts}" var="contact">
        <!--  Note: {#contact.firstName} : this is 1-TIME data-binding to var contact-->
        <span> {#contact.firstName} </span>
        <!--
         - this is 1-TIME data-binding
         - cheap! 

        -->
    </aura:iteration>

</aura:component>

Note:

Don’t use a component’s init event and client-side controller to initialize an attribute that is used in an unbound expression.

The attribute will not be initialized. Use a bound expression instead.

Modular Code

Share Code and Data Between Components

JS library

Write simple simple counter JS library

// simple Counter lib

// singleton
window.counter = (function() {
   var value = 0;
   return { // returns 2 functions

       inc: function() {
            return ++value;
       },
       dec: function() {
        return --value;
       },
       getValue : function () {
           return value;
       }
   }
} () );

// non-singleton
window.counter2 = function() {
    var value = 0;

    return {
        inc : function() { return ++value; },
        dec : function() { return --value; },
        getValue: function () { return value; }
    };

 };

lex-js-lib-1

Counter Component using this Simple JS library

<aura:component  implements="flexipage:availableForAllPageTypes" 
                access="global">

    <!-- require this js lib -->
    <ltng:require scripts="{!$Resource.gw19__Counter}" />

    <!-- counter value -->
    <aura:attribute name="value" type="Integer" default="0" />
    <aura:attribute name="help" type="String" default="Help!" />

    <!-- component intialization 
    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />
    -->

    <div>
        <div>Value: {!v.value}</div>
        <lightning:button  class='slds-button slds-button_outline-brand' onclick="{!c.getValue}"> Get Value </lightning:button>
        <lightning:button  class='slds-button slds-button_brand' onclick="{!c.inc}"> Increment Value </lightning:button>
        <lightning:button  class='slds-button slds-button_destructive' onclick="{!c.dec}"> Decrement Value </lightning:button>

        <lightning:button  class='slds-button slds-button_success' onclick="{!c.showHelp}"> Help </lightning:button>
        <div> <hr/>{!v.help}</div>
    </div>

</aura:component>

Component Controller


({
    doInit : function(component, event, helper) {
         component.set('v.value', 0);
    },
    getValue : function(component, event, helper) {
        var value = window.counter.getValue();
        component.set('v.value', value);

    },

    inc : function(component, event, helper) {
        var value = window.counter.inc();
        component.set('v.value', value);
    },

    dec : function(component, event, helper) {
        var value = window.counter.dec();
        component.set('v.value', value);
    },

    showHelp : function(component, event, helper) {
        var help = "Here comes help content for this component...";
        component.set('v.help', help);
    },




})

Testing App


<aura:application extends="force:slds">
    <gw19:Counter />
</aura:application>

Testing App CSS


.THIS {
    padding:10px;
    margin:30px;
    border:5px solid steelblue;
    border-radius:10px;
}

Demo with Aura App

lex-js-lib-1

Demo in Lightning Page built with App Builder

lex-js-lib-1

Counter2 Component using this Simple JS library

<aura:component  implements="flexipage:availableForAllPageTypes" 
                access="global">

    <!-- require this js lib  and call jsLoaded to create the window.counter2 object
         so window.counter2 object is unique to this component instance - not a singleton

    -->
    <ltng:require scripts="{!$Resource.gw19__Counter}"
                  afterScriptsLoaded="{!c.jsLoaded}" />

    <!-- counter value -->
    <aura:attribute name="value" type="Integer" default="0" />
    <aura:attribute name="help" type="String" default="Help!" />

    <!-- component intialization 
    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />
    -->

    <div>
        <div>Value: {!v.value}</div>
        <lightning:button  class='slds-button slds-button_outline-brand' onclick="{!c.getValue}"> Get Value </lightning:button>
        <lightning:button  class='slds-button slds-button_brand' onclick="{!c.inc}"> Increment Value </lightning:button>
        <lightning:button  class='slds-button slds-button_destructive' onclick="{!c.dec}"> Decrement Value </lightning:button>

        <lightning:button  class='slds-button slds-button_success' onclick="{!c.showHelp}"> Help </lightning:button>
        <div> <hr/>{!v.help}</div>
    </div>

</aura:component>

Component Controller


({
    doInit : function(component, event, helper) {
         component.set('v.value', 0);
    },

    jsLoaded: function(component, event, helper) {
        // here create a new object for window.counter2 and
        //  store in component.counter object
        component.counter = new window.counter2();

    },

    getValue : function(component, event, helper) {
        var value = component.counter.getValue();
        component.set('v.value', value);

    },

    inc : function(component, event, helper) {
        var value = component.counter.inc();
        component.set('v.value', value);
    },

    dec : function(component, event, helper) {
        var value = component.counter.dec();
        component.set('v.value', value);
    },

    showHelp : function(component, event, helper) {
        var help = "Here comes help content for this component...";
        component.set('v.help', help);
    },




})

Demo in Lightning Page built with App Builder showing both singleton and non-singleton window.counter

lex-js-lib-1

Non-visual component - DataService components


Use <aura:method> to define a method as part of a component's API. This enables you to directly call a method in a **component’s client-side controller instead of firing and handling a component event**.

 Using <aura:method> simplifies the code needed for a parent component to **call a method on a child component** that it contains.


**AccountDataService component - has no UI - non-visual**

```xml
<aura:component  controller="AccountCtrl">

  <!-- this findAll method another component can invoke -->
   <aura:method name="findAll" action="{!c.getAccounts}">
      <aura:attribute name="callback" type="function" />
   </aura:method>

</aura:component>

Server-side controller AccountCtrl


public class AccountCtrl {

    @AuraEnabled
    public static List<Account> svrGetAllAccounts(){
        List<Account> accounts = [SELECT Name FROM Account];
        return accounts;  
    }
}

AccountDataService component controller


({
   getAccounts : function (component, event, helper) {
       var params = event.getParam("arguments");
       var action = component.get("c.svrGetAllAccounts");
       action.setStorable();

       action.setCallback (this, function(response){
           if (response.getState() === 'SUCCESS') {
               params.callback(null, response.getReturnValue() );
           } else {
               params.callback(response.getError() );
           }
       });

       $A.enqueueAction(action);
   }

})

AccountList component using AccountDataService


<!-- has no controller but it uses AccountDataService

   NOTE: Generally Component can have only one Service-side controller

   This way a component can have make use many DataServices

 -->
<!-- has no controller but it uses AccountDataService

   NOTE: Generally Component can have only one Service-side controller

   This way a component can have make use many DataServices

 -->
<aura:component  implements="flexipage:availableForAllPageTypes" 
                access="global">

    <aura:attribute name="accounts" type="Account[]" />
    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />

    <gw19:AccountDataService aura:id="ads" />
    <!--
     <gw19:ContactDataService aura:id="cds" />
     -->
    <!--
     NOTE: Generally Component can have only one Service-side controller

     This way a component can have make use many DataServices
    -->

    <ul>
        <aura:iteration items="{!v.accounts}" var="account">
            <li>{!account.Name}</li>
        </aura:iteration>

    </ul>


</aura:component>

Controller for AccountList Component


({

    doInit : function (component, event, helper) {
        var ads = component.find("ads"); // get the AccountDataService
        // call findAll method on  AccountDataService
        ads.findAll($A.getCallback( function (error, data) {
          component.set('v.accounts', data);  
        }));
    }
})

TestApp


<aura:application extends="force:slds">
    <gw19:Counter />

    <gw19:Counter2 />

    <gw19:AccountList />
</aura:application>

Events

Events type selection

  • Application-Events are broadcast time : Costly
  • If Component-B is included in Component-A (have a parent-child relationship):

    • Component-B was to say something to Component-A - Use Component-Event, not Application-Event
  • If Component-A and Component-B do not have a parent-child relationship:

    • Component-B was to say something to Component-A - Use Application-Event
    • This is typical App-Builder use case
  • If you using Application-level outside the App-Builder use-case, think twice and consider Component-Events

  • If two systems: System-A and System-B want to communicate each other, user Platform Events

DataService demo


DataService demo

System to System event - platform event and Component-Event Example

Subscriber component

<aura:component  implements="flexipage:availableForAllPageTypes,flexipage:availableForRecordHome" 
                 controller="SessionCtrl"
                access="global">

<!--

SessionController provides svrGetSessionId()
-->

<aura:attribute name="eventName" type="String" />

<ltng:require scripts="{!$Resource.cometd + '/cometd.js' } " 
       afterScriptsLoaded="{!c.onJSLoaded}"

/>

<aura:registerEvent name="onmessage" type="gw19:MessageEvent" />

</aura:component>

Subscriber component controller


({
   onJSLoaded: function(component, event, helper) {
       var action = component.get("c.svrGetSessionId");

       action.setCallback(this, function(response) {
           // get sessionId
           var sessionId = response.getReturnValue();
           // get the cometd object
           var cometd = new window.org.cometd.CometD();
           // now configure the cometd object
           cometd.configure({
                url: window.location.protocol + '//' +
                     window.location.hostname +
                     '/cometd/41.0/',
                requestHeader: { Authorization: 'OAuth ' + sessionId },
                appendMessageTypeToURL : false

           });

           // comet handshake
           cometd.handshake($A.getCalback (function (status){
               if (status.successful) {
                   var eventName = component.get("v.eventName");
                   // eventName will Mix_Approved__e here...
                   cometd.subscribe('/event/' + eventName , $A.getCallback(
                       // get the message from the Platform Event (Mix_Approved__e)
                       //   and fire the Component-event (onmessage)
                       function( message) {
                           console.log(message);
                           var messageEvent = component.getEvent("onmessage");
                           messageEvent.setParam("message", message);
                           //fire: component-event the MixPath component listens to this component-event
                           //     after receiving this event it changes the step in the Path
                           messageEvent.fire();
                       }
                   ) 

                   );
               }

           }));



       }); // action.setCallback...

       $A.enqueueAction(action);



   },

})

SessionCtrl - server-side controller

public class SessionCtrl {
    @AuraEnabled
    // exposes this method to Lightning component controller
    public static String svrGetSessionId(){
        // uses std UserInfo utility class to get sessionId
        return UserInfo.getSessionId();
    }
}

MixPath Component

<aura:component  implements="flexipage:availableForAllPageTypes,flexipage:availableForRecordHome" 
                access="global">

    <aura:attribute name="recordId"  type="Id" />
    <aura:attribute name="record"  type="Object" />
    <aura:attribute name="steps"  type="String[]" default ="['Draft', 'Submitted to Mfr', 'Approved by Mfr', 'Ordered']" />

   <!-- The Subscriber will translate this Platform event(Mix_Approved__e) to Component-Event (onMixApproved)

   -->
    <gw19:Subscriber eventName="Mix_Approved__e" onmessage="{!c.onMixApproved}" />

    <force:recordData
          aura:id="mixRecord"
          recordId="{!v.recordId}"
          targetFields="{!v.record}"
          fields="['Id', 'Status__c']"

          recordUpdated="{!c.onRecordUpdated}"
     />

     <gw:Path steps="{!v.steps}" currentStep="{!v.record.Status__c}"
                stepChange="{!c.onStepChange}"

     />





</aura:component>

Demo of using Platform Events and Component Events

  • Subscribe to the event created by the Manufacturing event - Platform Event
  • On the receipt that Platform Event, a component-event is fired

PE demo

Resources

Videos

Advanced Lightning Components

results matching ""

    No results matching ""