10

I'm using JavaScript's setInterval to run some client code every few seconds in a Lightning Component and also have clearInterval logic working to remove that timer within the component.

But if user navigates away I also need to run the clearInterval (as otherwise my background logic continues to run when it shouldn't) but as posts such as How do I get the Lightning Component "destroy" system event to fire? suggest, that turns out to be quite hard.

Any working patterns for this?

  • I thought that when you navigated away the component is destroyed and always assumed the interval died with it....Going to have to check this out in my implementations of it...Thanks for the questio – Eric Nov 25 '17 at 0:17
  • In my point of view ,you want to clear interval in unrender as Mr.praveen suggested refer this salesforce.stackexchange.com/questions/153439/… – vignesh Nov 25 '17 at 0:22
7

Usually, the component does not get destroyed once you navigate to a different view. When I tested at my end it usually takes around 9 distinct navigation(eg: visiting 9 different tabs) for the component to become invalid (i.e destroy).

In general, when component is destroyed it is necessary to remove event handler or callbacks attached to the window,document,.. objects. The right place to do it is in the unrender handler of a component as mentioned here

You could do two things:

1) Use unrender to clear the setInterval. (NOTE: Even though it does not get cleared once you navigate. Since each view has its own version of the window object (see below attachment), I think it might not be issue).

2) Listen to aura:locationChange event and clear the interval.

Below

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes">
<aura:attribute name="counter" type="Integer" default="0"/>
<aura:attribute name="setIntervalId" type="Integer"/>
<aura:handler name="init" value="{!this}" action="{!c.initMethod}" />
<aura:handler event="aura:locationChange" action="{!c.handleDestroy}"/>

    Counter : {!v.counter}
</aura:component>

controller.js

({
    initMethod : function( cmp, evt, h ) {
        var i = 0;
        var interval = window.setInterval(
            $A.getCallback(function() {

                var value = i++;
                console.log(value + ' from component with id ' + cmp.getGlobalId());
                // code to execute periodically goes here
                cmp.set("v.counter", value);

            }), 2000
        );   


        cmp.set("v.setIntervalId", interval) ;   

    },
    handleDestroy : function( cmp, evt, h ) {
        console.log('clear interval due to navigation');
        window.clearInterval(cmp.get("v.setIntervalId"));
    },
})

renderer.js

({
    // Your renderer method overrides go here
    unrender: function (cmp,helper) {
        this.superUnrender();
        console.log('clear interval due to unrender');

        window.clearInterval(cmp.get("v.setIntervalId"));
    }
})

An interesting note, when I added the above component to two tabs in LEX; each component's setTimeout logs separately without overriding each other. Looks like Lightning creates window instance for a view in LEX.

enter image description here

  • Thanks Praveen - another really helpful post. I can confirm that the only mechanism I could get to work is listening to aura:locationChange which feels kinda wrong. But as that's all that is available... – Keith C Nov 25 '17 at 14:45
  • I don't think it's true that each component has its own window object. Every cmp in the same namespace shares the same Lockerized window object (or at least there is an interchange mechanism available). But the counter variable i belongs to the scope of the initMethod that declared it. Since each cmp instance ran initMethod separately, two different independent scopes exist for i and the interval. – Charles T Nov 25 '17 at 16:35
  • 1
    Also aura:locationChange may have an unpredictable future... I've heard murmurs that the LEX team is working on throwing away the hash URLs and coming up with better deep linking schemes. – Charles T Nov 25 '17 at 16:45
  • May be every time you navigate to a new view, Lightning creates a new window. @CharlesT what you say makes sense. I'll amend the answer. – Praveen Nov 25 '17 at 16:49
  • Ok here's a detail we all missed. Put in your cmp a div with an identifiable ID like myComponentInstance. Now in your browser inspector, find that ID. You will see that when you start navigating to other tabs, the div becomes hidden by CSS but still in the DOM. Keep tabbing around and you'll see that at the same moment the component finally identifies as destroyed, it's finally been removed from the DOM. – Charles T Nov 25 '17 at 23:34
3

Try the component.isValid() method. Pass a reference to the component into the method that executes on the interval. Have it check for validity every time it runs.

https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/js_cmp_isvalid.htm

So adapting that code...

init : function(cmp){
    var i = 0;
    var interval = window.setInterval(
        $A.getCallback(function() {
            if (!cmp.isValid()){
                window.clearInterval(interval);
                console.log('Component no longer valid!');
                return;
            }
            // code to execute periodically goes here
            console.log(i++);
        }), 2000
    );     
    // other init code goes here
}

Once the component has been unrendered the next validity check should fail.

I tested this by pasting it into the init handler of an existing component and then I monitored my browser console while going into Lightning App Builder. When I added the component to the page I could see the counter ticking in console. Once I clicked X to remove the component I got the "no longer valid" message and it stopped ticking.

  • I like the look of this but just tested it where I'm navigating away from the component (running in an App Builder page) and the component remains valid. Perhaps component destruction is a garbage collection sort of thing. – Keith C Nov 25 '17 at 14:17
  • Interesting and strange. This might be something the Lightning team would care to review and comment on. Until then maybe we just need to use "the kitchen sink"... throw every possible detection mechanism at it, and catch whatever triggers first. – Charles T Nov 25 '17 at 16:32
  • My guess is that the component lifecycle is optimised primarily for performance. As well as the problem with the setInterval code, I was also getting old instances (nominally discarded by the user) of a component responding to application events intended only for current instances because of the delayed destroy of the old instances. – Keith C Nov 25 '17 at 16:40
  • Just another hacky thought... it seems we have behaviours that take place when the component has left the DOM but has not been destroyed. I wonder if we could fetch a reference to the component's DOM element (it's in our namespace so Locker allows it) and check where it's anywhere in the DOM at all, hidden or not. So something like: document.body.contains(cmp.getElement()) – Charles T Nov 25 '17 at 23:12
  • Huh, weird. It still identifies as being in the DOM after flipping to a new tab! – Charles T Nov 25 '17 at 23:17

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.