Nick Novotny Nick Novotny - 2 months ago 10
TypeScript Question

JavaScript function gets called multible times by event listener even though I reference the function

I’m adding an event listener on each time I create an angular controller. Each time I leave this page and come back to it, a new event listener gets added cause the constructor is called again.

When this event gets triggered, the same event gets invoked twice, and if I leave and come back, it gets invoked 3 times… etc.. I only want it to always get invoked once.

Here is the code to add the event listener, and the Listener function it calls:
(FYI I'm using TypeScript)

In Constructor:

this.$window.addEventListener("message", this.processApi, false);


Function Called:

processApi = (e) => {
this.processApiMessage(e.data);
};


I read that I should call a reference to a function instead of typing out the function itself, so both reference the same instance of a function, but the same event listener is being called multiple times.

When I do developer tools in chrome, and go to EventListners, and go to message section, I see a new Window element every time I hit the constructor. I am able to delete each of the EventListers through developer tools but can't seem to get it to work through code when I do:

this.$window.removeEventListener("message", this.processApi, false);


I found out that if I refresh the page, all the event listeners clear and my one in the constructor is created so it works fine.

I'm using angular, and was using the $location service to navigate to the url that hit my controller, so a quick fix was to replace $location.url("url") to window.location.href("url")

This seems to work cause the page gets refreshed when I navigate to it. I would rather keep the $location for routing, and only have my event listener get hit once even though the angular constructor is hit multiple times.

Answer

You need to remove the event listener when the scope is $destroyed. So, in the controller constructor, you need to inject the $scope object. And in the constructor, do something like this:

$scope.$on('$destroy', () => 
  $window.removeEventListener("message", this.processApi));

To be sure, there are several ways of creating a controller. The most common one (especially if you are using TypeScript) is to create a class for the controller.

I would also consider adding the listener to the rootScope instead of $window, which is more the Angular way of doing things. Putting it all together, it would look like this:

class MyController {
  constructor($rootScope, $scope) {
    'ngInject';

    let unsubscriber = $rootScope.$on('message', this.processApi, false);    
    $scope.$on('$destroy', () => unsubscriber());  
  }
  ...
}