Search code examples
angularjsangularjs-scope

Avoid $rootScope.$on — it risks memory leaks


Angularjs: Subscribing to a Service event in Directive doesn't update

I am trying to $emit/$broadcast an event in a Service and subscribe to it inside a Directive but it is not being fired.

In this case I try both $emit and $broadcast but although both are seen in the controller, neither are seen in the directive.

Anyone have any idea how to accomplish this?

angular.module('my_app', [])

.service('my_service', ['$rootScope', function($rootScope) {
    this.do_something = () => {
        $rootScope.$emit('myEvent');
        $rootScope.$broadcast('myEvent');
    }
}])

.directive('myDirective', ['$rootScope', function($rootScope) {
    return {
        scope: {
            some_var: '@?'
        },
        link: function(scope, element, attrs, controller) {
            $rootScope.$on('myEvent', ()=>{
                console.log('event fired in directive');
            })
        }
    }
}])

.controller('my_controller', ['$rootScope', 'my_service', function($rootScope, my_service) {
    $rootScope.$on('myEvent', ()=>{
        console.log('event fired in controller');
    })

    my_service.do_something();

}])

And the html:

<my-directive ng-if="some_expression"></my-directive>

In this case I only get:

event fired in controller
event fired in controller

Solution

  • Avoid $rootScope.$on — it risks memory leaks

    In the course of its operation, AngularJS adds and removes DOM with their attached directives and controllers. The listener functions added by $rootScope.$on are not automatically removed when a directive or controller is removed. This can result in memory leaks and undesired behavior.

    To avoid memory leaks, add event listeners to the $scope of the controller, not $rootScope:

    .controller('my_controller', ['$scope', 'my_app_service', function($scope, my_app_service) {
        $scope.$on('myEvent', ()=>{
            console.log('event fired in controller');
        }
        my_app_service.do_something();    
    }])
    

    With directives, add event listeners in the linking function:

    .directive('myDirective', function() {
        return {
            link: postLink
        }
        function postLink(scope, elem, attrs) {    
            scope.$on('myEvent', ()=>{
                console.log('event fired in directive');
            });
        }
    })
    

    From the Docs:

    Scope Events Propagation

    Scopes can propagate events in similar fashion to DOM events. The event can be broadcasted to the scope children or emitted to scope parents.

    For more information, see


    Update

    I corrected my question as I do actually have the event listener in the linking function. But still not working.

    Be sure that the name of the directive is in camelCase in the JavaScript:

    .̶d̶i̶r̶e̶c̶t̶i̶v̶e̶(̶'̶m̶y̶_̶d̶i̶r̶e̶c̶t̶i̶v̶e̶'̶,̶ ̶[̶f̶u̶n̶c̶t̶i̶o̶n̶(̶)̶ ̶{̶
    .directive('myDirective', [function() {
       // ...
    }])
    

    And kebab-case in the HTML:

    <my-directive>
    </my-directive>