Angular $rootScope $on listeners in 'destroyed' controller keep running
Asked Answered
M

2

1

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';
});
Masry answered 28/12, 2016 at 16:23 Comment(4)
can you demonstrate your logic/problem through a plunk or fiddlePetromilli
Please provide a minimal reproducible exampleRuelas
Sorry, actually it is with a $rootScope.$on listener. I will update with a plnkr and edit.Masry
Possible duplicate of How can I unregister a broadcast event to rootscope in AngularJS?Ornie
O
3

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.

Ornie answered 28/12, 2016 at 17:21 Comment(3)
@Masry Please re-review my answer, as I changed it to match your changes. Please be more specific with questions in the future.Ornie
Thanks for the response but my question is specifically why the listeners keep running after the controller appears to get destroyed, so I am not looking for a solution but an explanation for what is happening. Unfortunately I am already aware of how to unregister listeners and watches. My question is more theoretical, as I haven't been able to find the answer in angular docs.Masry
Then @Masry your answer is that the $rootScope is global to your entire application, and registering a listener against the $rootScope has no binding against your isolated scopes unless you manually do so. If you were to place your listener on your isolated scope, such as in $scope.$on, then your listener will be destroyed when the scope is. Your question does not specify that you are wondering why more than you are wondering how.Ornie
P
0

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.

Piranesi answered 28/12, 2016 at 17:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.