7

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)

  • did you did event.stopPropagation after handling? – Manjot Singh Oct 4 '17 at 11:37
  • @ManjotSingh no, nothing like this. As mentioned, I am able to handle the event properly. – Basti Oct 4 '17 at 11:43
  • i am saying just stop event after handling – Manjot Singh Oct 4 '17 at 13:31
  • @ManjotSingh please explain why this would help here. – Basti Oct 4 '17 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 '17 at 8:02
9
+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
    );
},
  • 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. – Basti Oct 9 '17 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. – Basti Oct 9 '17 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 '17 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 '17 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 '17 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.");
},
  • Didn't work for me :( – Basti Nov 29 '17 at 13:00
  • 1
    Didn't work for me either – Robs Apr 27 '18 at 9:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.