12

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?

2
  • 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
    Commented Nov 25, 2017 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
    Commented Nov 25, 2017 at 0:22

2 Answers 2

8

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

8
  • 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
    Commented Nov 25, 2017 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
    Commented Nov 25, 2017 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
    Commented Nov 25, 2017 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
    Commented Nov 25, 2017 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
    Commented Nov 25, 2017 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.

6
  • 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
    Commented Nov 25, 2017 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
    Commented Nov 25, 2017 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
    Commented Nov 25, 2017 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
    Commented Nov 25, 2017 at 23:12
  • Huh, weird. It still identifies as being in the DOM after flipping to a new tab!
    – Charles T
    Commented Nov 25, 2017 at 23:17

You must log in to answer this question.

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