Angular $rootScope $on listeners in 'destroyed' controller keep running

3.6k views Asked by At

I have my angular app set up with an ng-view directive in the index.html, with views configured with $routeProvider config and each view having its own controller. In one view controller, I have a $rootScope.$on listener that watches for events. I expect this listener to no longer run when I change views, but this is not the case. The watch still keeps calling code when it is triggered, even when I have switched to a view that is no longer using that controller.

I have set up some test code to listen to the $destroy event, and I can see that the $destroy event is being triggered, but the listener function still runs. I have printed out $scope.$id in both the watch function and the $destroy event listener, and I can see that the IDs match. So it seems that even after the $destroy event occurs for the specific scope, the $on listener on it still keep running. Why is this the case? I was under the impression that when navigating to a different view, or in any case the controller is destroyed, then listeners for it are cleared.

Plnkr: http://plnkr.co/edit/TxssxQJY5PVLTAIAr4xD?p=preview

var animateApp = angular.module('animateApp', ['ngRoute', 'ngAnimate']);

animateApp.config(function($routeProvider) {
    $routeProvider
        .when('/', {
            templateUrl: 'page-home.html',
            controller: 'mainController'
        })
        .when('/about', {
            templateUrl: 'page-about.html',
            controller: 'aboutController'
        })
        .when('/contact', {
            templateUrl: 'page-contact.html',
            controller: 'contactController'
        });

});

angular.module('animateApp').service('service', ['$interval', '$rootScope', service]);
function service ($interval, $rootScope) {
    var service = {};
    service.abc = 1;
        $interval(function() {
        service.abc++;
        $rootScope.$broadcast('service.abc', service.abc);
    }, 500);

    return service;

}


animateApp.controller('mainController', function($scope, $rootScope, service) {
    $scope.pageClass = 'page-home';
    $scope.$on('$destroy', function iVeBeenDismissed() {
                        console.log('goodbye ' + $scope.$id);
      // release resources, cancel request...
    })

    $rootScope.$on('service.abc', function (newval) {
        console.log($scope.$id);
    })
});

animateApp.controller('aboutController', function($scope) {
    $scope.pageClass = 'page-about';
});

animateApp.controller('contactController', function($scope) {
    $scope.pageClass = 'page-contact';
});
2

There are 2 answers

0
Tome Pejoski On

You should add a $destroy event listener in your controllers:

    scope.$on('$destroy', function () {
      element.off('blur'); // example
      element = null;
    });

With this you'll manually make sure that there will be no more active $watchers on inactive controllers.

3
WebWanderer On

So the question was completely changed so that the context was entirely different, so my previous answer no longer relates to the question really, except that it applies quite the same way.

You can unregister your listener in the same respect as my answer below.

$rootScope.$on() returns an unregister function.

Therefore, your mainController can be re-written as:

animateApp.controller('mainController', function($scope, $rootScope, service) {
    $scope.pageClass = 'page-home';
    var unregister = $rootScope.$on('service.abc', function (newval) {
        console.log($scope.$id);
    });
    $scope.$on('$destroy', unregister);
});

PREVIOUS ANSWER

You should make sure that you unbind your watch when the scope of your controller is destroyed.

When creating a watch, angular returns an unbinding function for the watch you created. This is covered pretty well here.

Create a reference to the watch you create:

var unbinder = $scope.$watch("foo.bar", doStuff);

Watch for your scope's destruction as you were, but call your unbinder there.

$scope.$on("$destroy", function(){
    if(typeof unbinder === "function")
        unbinder();
});

Once your unbinder function is called, your watch will be removed.