Improve this AngularJS factory to use with socket.io
Remove the socket listeners whenever the controller is destroyed. You will need to bind the $destroy
event like this:
function MyCtrl($scope, socket) { socket.on('message', function(data) { ... }); $scope.$on('$destroy', function (event) { socket.removeAllListeners(); // or something like // socket.removeListener(this); });};
For more information check the angularjs documentation.
You might be able to handle this with a minimal amount of work by wrapping up a Scope and watching for $destroy
to be broadcast, and when it is, only removing from the socket the listeners that were added in the context of that Scope. Be warned: what follows hasn't been tested--I'd treat it more like pseudocode than actual code. :)
// A ScopedSocket is an object that provides `on` and `emit` methods,// but keeps track of all listeners it registers on the socket.// A call to `removeAllListeners` will remove all listeners on the// socket that were created via this particular instance of ScopedSocket.var ScopedSocket = function(socket, $rootScope) { this.socket = socket; this.$rootScope = $rootScope; this.listeners = [];};ScopedSocket.prototype.removeAllListeners = function() { // Remove each of the stored listeners for(var i = 0; i < this.listeners.length; i++) { var details = this.listeners[i]; this.socket.removeListener(details.event, details.fn); };};ScopedSocket.prototype.on = function(event, callback) { var socket = this.socket; var $rootScope = this.$rootScope; var wrappedCallback = function() { var args = arguments; $rootScope.$apply(function() { callback.apply(socket, args); }); }; // Store the event name and callback so we can remove it later this.listeners.push({event: event, fn: wrappedCallback}); socket.on(event, wrappedCallback);};ScopedSocket.prototype.emit = function(event, data, callback) { var socket = this.socket; var $rootScope = this.$rootScope; socket.emit(event, data, function() { var args = arguments; $rootScope.$apply(function() { if (callback) { callback.apply(socket, args); } }); });};app.factory('Socket', function($rootScope) { var socket = io.connect(); // When injected into controllers, etc., Socket is a function // that takes a Scope and returns a ScopedSocket wrapping the // global Socket.IO `socket` object. When the scope is destroyed, // it will call `removeAllListeners` on that ScopedSocket. return function(scope) { var scopedSocket = new ScopedSocket(socket, $rootScope); scope.$on('$destroy', function() { scopedSocket.removeAllListeners(); }); return scopedSocket; };});function MyController($scope, Socket) { var socket = Socket($scope); socket.on('message', function(data) { ... });};
I would add a comment to the accepted answer, but i can't. So, i'll write a reply.I had the same problem and the easiest and simplest answer i found is the one that you can find here, on another post, provided by michaeljoser.
I'll copy it below for convenience:
You have to add removeAllListeners to your factory (see below) and have the following code in each of your controllers:
$scope.$on('$destroy', function (event) {socket.removeAllListeners();});
Updated socket factory:
var socket = io.connect('url'); return { on: function (eventName, callback) { socket.on(eventName, function () { var args = arguments; $rootScope.$apply(function () { callback.apply(socket, args); }); }); }, emit: function (eventName, data, callback) { socket.emit(eventName, data, function () { var args = arguments; $rootScope.$apply(function () { if (callback) { callback.apply(socket, args); } }); }) }, removeAllListeners: function (eventName, callback) { socket.removeAllListeners(eventName, function() { var args = arguments; $rootScope.$apply(function () { callback.apply(socket, args); }); }); } };});
It saved my day, i hope it will be useful to someone else!