5

Plnkr example build: http://plnkr.co/edit/gB7MtVOOHH0FBJYa6P8t?p=preview

Following the answer here, I created some $broadcast events to allow actions in the main $scope to close popovers in sub $scopes. However I want to make sure I clean up all my events and not have anything lingering that should not.

I have a popover directive, once the popover is activated I send out:

vs.$emit('popoverOpen');

Then in the main app module ($rootScope), I listen for it here:

vs.$on('popoverOpen',function(events,data) {

    // if 'popoverOpen' is heard, then activate this function
    // which on click $broadcasts out even 'bodyClick'
    // but also destroy the 'popoverOpen' event
    vs.bodyClick = function() {
        $rootScope.$broadcast('bodyClick');
        $rootScope.$$listenerCount.popoverOpen=[];
    };
});

Back in the popover Directive, here is my bodyClick listener:

vs.$on('bodyClick', function() {
    vs.searchPopoverDisplay = false;
    $rootScope.$$listenerCount.bodyClick=[];
});

^ I also have code in there attempting to kill the bodyClick event, however to no avail :(

As you can see below, I've removed bodyClick and popoverOpen from the $$listenerCount (there was never anything in the $$listener object.

However the user is still able to access the vs.bodyClick() function in the main app rootScope, even though popoverOpen should have been removed.


So on first load:

  • If the user clicks around without opening any popover, bodyClick is never captured / transmitted
  • After opening a popover, bodyClick is active
  • If the user clicks on the body, that event is sent out and closes the popover
  • However, now even with NO popovers open, if the user clicks on the body that bodyClick event keeps getting sent out

enter image description here

How would you properly remove the events after the bodyClick event has been sent out to close the popover?


UPDATE I also tried this (https://github.com/toddmotto/angularjs-styleguide#publish-and-subscribe-events), but still the bodyClick event keeps getting sent out after the popover is closed and supposedly destroyed.

popoverDirective function still gets called:

vs.$on('bodyClick', function() {

    console.log('bodyClick received ...');
    console.log($rootScope.$$listener);
    console.log($rootScope.$$listenerCount);

    vs.searchPopoverDisplay = false;

    var unbind = $rootScope.$on('popoverOpen', []);
    vs.$on('$destroy', unbind);
    // $rootScope.$$listenerCount.bodyClick=[];
});

enter image description here

Community
  • 1
  • 1
Leon Gaban
  • 36,509
  • 115
  • 332
  • 529
  • There is a note on Todd Motto's Angular guide that mentions destroying publish and subscribe events. It might be what you are looking for. https://github.com/toddmotto/angularjs-styleguide#publish-and-subscribe-events – Matthew Green May 15 '15 at 19:29
  • Thanks! Hmm I tried out his `destroy` code, but didn't work.. had to also fix a syntax error there, but still no luck... added what I tried above. – Leon Gaban May 15 '15 at 19:35
  • Building a plnkr example now http://plnkr.co/edit/gB7MtVOOHH0FBJYa6P8t?p=preview – Leon Gaban May 15 '15 at 20:05
  • So, I added a console to your `$destroy` listener in your plunkr and it never gets called. So then I added a `$scope.$destroy()` after your `bodyClick` call, and poof! No more alerts. Not sure if it has the desired after effect though... [my plunkr](http://plnkr.co/edit/0EDELIikFRQgdmrV8GM5?p=preview) – Tony May 15 '15 at 20:17

1 Answers1

3

A few notes about your plunkr:

  1. None of your controllers go out of scope, thus none of them emit $destroy.
  2. You're listening for $destroy but attempting to emit destroy, which isn't the same. Remove the $ from the listener and you'll see the callback running.
  3. Because subController is a child of mainController destroying main destroys both and stops the button, along with everything else. This behavior is shown in this plunkr.

So in order to get the pseudo-$destroy to ping, I changed it to listen on just destroy.

Then, instead of calling your unbind, which wouldn't work in this case as we aren't actually $destroying anything, I simply replaced the bodyClick function, as it is bound to the <body>.

$scope.$on('destroy', function() {
  $scope.bodyClick = angular.noop();
});

This seems to have the desired effect. Updated Plunkr here.

Update: I discovered why it was indefinitely adding listeners...! The destroy listener was in the button callback. So each button press would add yet another callback. I moved the destroy listener outside of the popover listener. No more memory leaking!

Tony
  • 2,473
  • 1
  • 21
  • 34
  • Oh, that is what I'm trying to do, how do you destroy the scope for real? Thanks for the first part.. I wanted to stop a function from getting called, but I also don't want anymore events being called when they are not needed. – Leon Gaban May 15 '15 at 20:59
  • Simply: `$scope.$destroy();` – Tony May 15 '15 at 21:01
  • Yes, I've been mulling over this plunkr **a lot**. I think its structure is making it extremely difficult to really give you a solid answer. For example, you set a `$scope` function in the callback we're trying to unbind, which won't actually unset that variable, so the method still exists. Hopefully this can push you in the right direction. – Tony May 15 '15 at 21:02
  • Thanks, yeah now I'm looking at your plnkr and the alert never comes back when the user clicks the button. Alert only happens once, but it should alert everytime the button is clicked, just not on the rest of the body. I'll play with this... thanks! – Leon Gaban May 15 '15 at 21:04
  • Like I amended, the button is a child of the controller you're looking to destroy! So taking out the parent will also remove the children. I was expecting the button to be a sibling to main, not a child. _hint hint_ – Tony May 15 '15 at 21:05
  • 1
    @LeonGaban I found the major issue in the plunkr. I've updated the link. The issue is that we were listening for our destroy in the callback of the button press. Doh! – Tony May 15 '15 at 21:24
  • Thanks man! This is brilliant, I understand why it works in your plunk, just not sure why it doesn't in my code :( mind taking a moment to see if anything stands out in these 2 gists? `main`: https://gist.github.com/leongaban/bc3110add6d484c7d982 | `sub`: https://gist.github.com/leongaban/a603b08941d69ab96e8c for example in my code, I still see the console logs when I click on the body, even when the popover has already been hidden. The popover is turned on in line `73` of the 2nd gist file. – Leon Gaban May 15 '15 at 22:04
  • Main:Line 75. Change `vs.$on('$destroy'...)` to `vs.$on('destroy'...)` Or maybe add `destroy` just to have both. haha – Tony May 15 '15 at 22:39
  • GOT IT! `vs.bodyClick = angular.noop();` Needed to do that, I guess I thought `vs` was trying to use `$scope` and `$rootScope`, but finally got it working... updated that 1 line in my main file and boom :) event gone when popover is gone, and comes back when popover is turned on again :D thanks for all the help! – Leon Gaban May 16 '15 at 02:07