8

I'm changing data in a component and show a spinner as soon as changes happen.

The handler executes all the lines, but $A.util.removeClass(cmp.find("loading"), 'slds-hide'); seems to be fired after the reload is done. This is not what I expect, since my execution order is:

  • show spinner
  • update table data
  • hide spinner

Controller:

dataChanged: function(cmp, evt, helper) {
    $A.util.removeClass(cmp.find("spinner"), 'slds-hide');
    console.log(Date.now());

    var table = cmp.get("v.data");
    helper.updateTable(table, cmp);
    cmp.set("v.data", table);

    console.log(Date.now());
    $A.util.addClass(cmp.find("spinner"), 'slds-hide');
},

(when I remove the last line, the spinner shows up, so there is no typo etc.)

Result:

  • log: 1507546819580
  • updateTable
  • reload UI
  • log: 1507546821040
  • start & stop spinner (the same time, so no effect)

Where does the delay come from and how can I avoid it?

(This question is part 2 of this question, after some more testing I came closer to the root cause)

5
  • did you did event.stopPropagation after handling? Oct 4, 2017 at 11:37
  • @ManjotSingh no, nothing like this. As mentioned, I am able to handle the event properly.
    – itsmebasti
    Oct 4, 2017 at 11:43
  • i am saying just stop event after handling Oct 4, 2017 at 13:31
  • @ManjotSingh please explain why this would help here.
    – itsmebasti
    Oct 4, 2017 at 13:32
  • Does adding getElement() on to the end of your find() improve the performance? If that is just as slow, could you try cmp.find("spinner").getElement().classList.add('slds-hide'). I know this is against best practice as per the docs, but for this use case it might be okay...
    – marktuk
    Oct 12, 2017 at 8:02

2 Answers 2

11
+50

Lightning has some sort of digest cycle analogous to what you see in Angular and other front-end frameworks. When you make changes that affect the UI, they are not evaluated instantly but rather as part of the framework lifecycle.

As far as I know, when you fire an event it happens asynchronously. That means the current cycle continues executing and completes, and the async event doesn't evaluate in the UI until another lifecycle. Have you tried also logging the time during the steps of the dataChanged function to see the real sequence?

What's probably (approximately) happening is:

  • You fire the startWaiting event which is async
  • Your helper function updates the table
  • Your helper function sets the table data
  • The function finishes executing
  • The 1st framework lifecycle now reaches the stage where it re-renders the UI
  • The UI blocks while processing the re-render of the table
  • The re-render finishes and so does the 1st lifecycle
  • Now there is a startWaiting event to handle and the 2nd lifecycle starts
  • The $A.util.removeClass function sets the class parameter of the component
  • The 2nd lifecycle now re-renders

It sounds like what you really need is to make the spinner visible before re-rendering the table can block the UI. So I would flip this around. Fire the $A.util.removeClass immediately, not through an event. Then fire an event which completes the table update. If I'm right about this, the 1st lifecycle will complete quickly and show the spinner, while the table update will run in a 2nd lifecycle.

EDIT:


It is possible to break out of the Lifecycle as stated here

dataChanged: function(cmp, evt, helper) {
    $A.util.removeClass(cmp.find("spinner"), 'slds-hide');

    window.setTimeout(
        $A.getCallback(function() {
            var table = cmp.get("v.data");
            helper.updateTable(table, cmp);
            cmp.set("v.data", table);

            $A.util.addClass(cmp.find("spinner"), 'slds-hide');
        }), 0
    );
},
9
  • thanks for listing the execution order, the async part might clarify why all this happens. But the approach to call removeClass directly won't work since the inner component has no access to the outer components spinner element.
    – itsmebasti
    Oct 9, 2017 at 7:31
  • I also checked, if I could show a spinner in the same component, even there the same effect appears. lines are executed, but the class is not removed. I will make the question simpler with this information.
    – itsmebasti
    Oct 9, 2017 at 10:42
  • I see you've modified the code sample to no longer run the spinner as an event. You state the execution order is "show spinner, update table data, hide spinner". But that's not what Lightning does. The real execution order is "update spinner class data, update table data, update spinner class data, render all of it".
    – Charles T
    Oct 10, 2017 at 5:29
  • 1
    Humm... although this is not a highly recommended course of action, for argument's sake, what are your results when you do: window.setTimeout( $A.getCallback(function() { // inside here, populate the table and then re-add the slds-hide class }), 500 );
    – Charles T
    Oct 10, 2017 at 11:32
  • 1
    Interesting that $A.getCallback seems unnecessary. In general if it's not used it might go and change the data outside the rendering lifecycle. But Lightning does work in mysterious ways. :)
    – Charles T
    Oct 12, 2017 at 20:06
1

Ran into the same problem when trying to use a spinner to save a record. Going off the docs: https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/components_lifecycle.htm

A component is instantiated, rendered, and rerendered during its lifecycle. A component is rerendered only when there’s a programmatic or value change that would require a rerender, such as when a browser event triggers an action that updates its data.

Simply force a rerender after each remove/add class by updating the value of an attribute displayed in the view. E.g. A string that says "Spinner is spinning now..." when active.

dataChanged: function(cmp, evt, helper) {
    $A.util.removeClass(cmp.find("spinner"), 'slds-hide');
    cmp.set("v.spinnerDescription", "Spinner is spinning now...");

    var table = cmp.get("v.data");
    helper.updateTable(table, cmp);
    cmp.set("v.data", table);

    $A.util.addClass(cmp.find("spinner"), 'slds-hide');
    cmp.set("v.spinnerDescription", "Spinner is resting.");
},
2
  • Didn't work for me :(
    – itsmebasti
    Nov 29, 2017 at 13:00
  • 1
    Didn't work for me either
    – Robs
    Apr 27, 2018 at 9:21

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .