27

I'm polling for my data every 2 seconds to keep them updated on the page. My problem is when I visit another page the timeout stays active. How can i cancel my timeout when I visit an new page?

function IndexCtrl($scope, $timeout, RestData) {
    $scope.rd = {};

    (function getRestDataFromServer() {
        RestData.query(function(data){
            $scope.rd = data;
            $timeout(getRestDataFromServer, 2000);
        });
    })();
}

//EDIT I found a solution, but I'm not sure if it's a good one. When i save my timeout to the $rootScope, I can cancel it in all the other controllers.

function IndexCtrl($scope, $rootScope, $timeout, RestData) {
    $scope.rd = {};

    (function getRestDataFromServer() {
        RestData.query(function(data){
            $scope.rd = data;
            $rootScope.prom = $timeout(getRestDataFromServer, 2000);
        });
    })();
}

function newPageCtrl($scope, $rootScope, $timeout) {
    $timeout.cancel($rootScope.prom); 
}
fraherm
  • 671
  • 2
  • 11
  • 21

2 Answers2

65

There are couple of Angular events that are being broadcasted when route is being changed. You can listen for them within the IndexCtrl using $scope.$on and act accordingly:

$destroy event

var promise = $timeout(getRestDataFromServer, 2000);
...

$scope.$on('$destroy', function(){
    $timeout.cancel(promise);
});

$locationChangeStart

var promise = $timeout(getRestDataFromServer, 2000);
...

$scope.$on('$locationChangeStart', function(){
    $timeout.cancel(promise);
});

$timeout() returns a promise object. This object can be supplied to $timeout.cancel() function to cancel the timeout.

Stewie
  • 60,366
  • 20
  • 146
  • 113
15

Stewie's answer is perfect. I just wanted to share this simple helper function that I use instead of using $timeout directly, so that I never have to think about this issue again:

function setTimeout(scope, fn, delay) {
    var promise = $timeout(fn, delay);
    var deregister = scope.$on('$destroy', function() {
        $timeout.cancel(promise);
    });
    promise.then(deregister, deregister);
}

I added this function to a service called miscUtils, and I inject that service instead of injecting $timeout. Then, for example, to make an "update" function that runs every 30 seconds until $scope is destroyed:

update();
function update() {
    // do the actual updating here
    miscUtils.setTimeout($scope, update, 30000);
}

Edit for those confused about what's going on with deregister:

This function registers a listener for the $destroy event, but once the timeout has completed it is no longer necessary; there is no longer a timeout to cancel. scope.$on returns a function that, when called, deregisters that listener. So, promise.then(deregister) cleans up that no-longer-needed listener as soon as the timeout completes.

Eric Simonton
  • 5,702
  • 2
  • 37
  • 54
  • This sounds like a very good solution. Could you share all the code, like a working example, inkluding the service definition, so for me as a beginner it is easier to integrate it in my own application? Thank you! – SHernandez Jun 23 '14 at 22:27
  • The `deregister` variable confused me for a while, I thought was the function to cancel the timeout but it's not. It's the function to cancel the cancelling of the timeout. – dshepherd Mar 22 '16 at 19:07
  • @dshepherd please explain because I'm still confused. – natanavra Sep 27 '16 at 19:08
  • If the variable was named `deregisterDestroyListener` that might help? – dshepherd Sep 28 '16 at 06:31