[SalesForce] Refreshing Lightning Component based on field change

We have a Lightning Component showing a span tag with class slds-badge. We add another class to the tag depending on a field value of a Account. We show this component on the record page of said Account, so within the standard app, not a custom one.

Our component correctly renders the different colours based on the class returned from the controller when we open the Account page. Our challenge lies in the fact that we want to update the component when the field value changes. However, we are having trouble catching the event that fires (if there is an event in the first place) when the field change is saved.

We tried catching the aura:waiting or aura:doneWaiting events, which didn't really work as these events fire a lot (hundreds of times) during the page life cycle. The thing we tried next was to listen to the event fired when a toast is shown, which typically happens when a field change is succesful or a related object (like a task) is created. All in all this works, but we're not totally satisfied with the situation, because what happens if Salesforce decides to stop showing toasts after a record is saved?

Do you know of any more robust ways to listen to events on field change?

Component code

<aura:component implements="flexipage:availableForAllPageTypes,force:hasRecordId" access="global" controller="stagebar_Account_Lightning">
    <aura:attribute name="iscont" type="Boolean" default="true"/>
    <aura:attribute name="recordId" type="Id"/>

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

    <div class="slds">
        <ltng:require styles="/resource/SLDS103/assets/styles/salesforce-lightning-design-system-ltng.css"/>
            <span aura:id="isContacted" class="slds-badge">{!$Label.c.Is_contacted}</span>
    </div>
</aura:component>

JS Controller Code

({
    doInit : function(component, event, helper) {
        helper.getContacted(component);
    },

    recordSaved: function(component, event, helper) {
        helper.getContacted(component);
    }
})

The controller basically calls the init method again if it detects a toast.

Helper code

({
    getContacted : function(cmp){
        var action = cmp.get("c.accountIsContacted");
        action.setCallback(this, function(a){
            if(a.getState() === "SUCCESS"){
                var cmpTarget = cmp.find("isContacted");
                this.setBadgeColor(cmpTarget, a.getReturnValue());
            } 
        });
        $A.enqueueAction(action);
    },

//Color the badges, helper method.
setBadgeColor: function(target, retValue) {
    console.log("inside setBadgeColor");
    if(retValue == 'green'){
        $A.util.addClass(target, "slds-badge-green");
        $A.util.removeClass(target, "slds-badge-yellow");
        $A.util.removeClass(target, "slds-badge-grey");
        $A.util.removeClass(target, "slds-badge-red");
    }else if(retValue == 'yellow') {
        $A.util.removeClass(target, "slds-badge-green");
        $A.util.addClass(target, "slds-badge-yellow");
        $A.util.removeClass(target, "slds-badge-grey");
        $A.util.removeClass(target, "slds-badge-red");
    } else if(retValue == 'grey'){
        $A.util.removeClass(target, "slds-badge-green");
        $A.util.removeClass(target, "slds-badge-yellow");
        $A.util.addClass(target, "slds-badge-grey");
        $A.util.removeClass(target, "slds-badge-red");

    } else if(retValue == 'red'){
        $A.util.removeClass(target, "slds-badge-green");
        $A.util.removeClass(target, "slds-badge-yellow");
        $A.util.removeClass(target, "slds-badge-grey");
        $A.util.addClass(target, "slds-badge-red");
    }
}
])

Apex Controller (only relevant code)

@AuraEnabled
    public static String accountIsContacted() {
        if(currentAccount.LastActivityDate > Date.today().addYears(-1) && currentAccount.LastActivityDate != null){
            return badgeColor.green.name();
        } else {
            return badgeColor.yellow.name();
        }
    }

Best Answer

I recommend you look at this page in the Lightning Components Documentation: Calling a Server-Side Action.

Using parts of the example code provided, you could set up a server-side "echo" that gets returned to your client side controller when the action method is complete as described at the bottom of the 2nd code section on that page following where the action method is enqueued:

In the client-side controller, we use the value provider of c to invoke a server-side controller action. We also use the c syntx in markup to invoke a client-side controller action. The cmp.get("c.serverEcho") call indicates that we are calling the serverEcho method in the server-side controller. The method name in the server-side controller must match everything after the c. in the client-side call.

There are several notes on the page of importance. This note may one that applies to what's happening in your code:

If your action is not executing, make sure that you're not executing code outside the framework's normal rerendering lifecycle. For example, if you use window.setTimeout() in an event handler to execute some logic after a time delay, wrap your code in $A.getCallback(). You don't need to use $A.getCallback() if your code is executed as part of the framework's call stack; for example, your code is handling an event or in the callback for a server-side controller action.

One of things to note that could also be useful to you is the following:

Always add an isValid() check if you reference a component in asynchronous code, such as a callback or a timeout. If you navigate elsewhere in the UI while asynchronous code is executing, the framework unrenders and destroys the component that made the asynchronous request. You can still have a reference to that component, but it is no longer valid. Add an isValid() call to check that the component is still valid before processing the results of the asynchronous request.

Additionally, there's this:

setCallback() has a third parameter that registers the action state that invokes the callback. If you don't specify the third argument for setCallback(), it defaults to registering the SUCCESS and ERROR states. To set a callback for another state, such as ABORTED, you can call setCallback() multiple times with the action state set explicitly in the third argument. For example:

Should none of the above solve your issue, It would seem your only recourse will be to insert the record yourself in a synchronous operation if you want to improve performance and obtain reliable confirmation within your component that the action method has completed.

Related Topic