I want to use socket.io in AngularJS. I found the following factory:
app.factory(\'socket\', function ($rootScope) {
var socket = io.connect();
retur
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!
I solved this problem by checking whether listener already exists. If you have multiple controllers that are all loaded at the same time (think of different page modules that all utilize socketIO), removing all registered listeners on $destroy
would break the functionality of both the destroyed controller and all the controllers that are still loaded.
app.factory("SocketIoFactory", function ($rootScope) {
var socket = null;
var nodePath = "http://localhost:12345/";
function listenerExists(eventName) {
return socket.hasOwnProperty("$events") && socket.$events.hasOwnProperty(eventName);
}
return {
connect: function () {
socket = io.connect(nodePath);
},
connected: function () {
return socket != null;
},
on: function (eventName, callback) {
if (!listenerExists(eventName)) {
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);
}
});
})
}
};
});
This could be further improved by tracking which listeners were registered by which controller and removing only listeners that belong to destroyed controllers to clean up the memory.
I am doing this to avoid duplicated listeners and works pretty well.
on: function (eventName, callback) {
//avoid duplicated listeners
if (listeners[eventName] != undefined) return;
socket.on(eventName, function () {
var args = arguments;
$rootScope.$apply(function () {
callback.apply(socket, args);
});
listeners[eventName] = true;
});
},
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 just solved a similar problem before I read this. I did it all in the Service.
.controller('AlertCtrl', ["$scope", "$rootScope", "Socket", function($scope, $rootScope, Socket) {
$scope.Socket = Socket;
}])
// this is where the alerts are received and passed to the controller then to the view
.factory('Socket', ["$rootScope", function($rootScope) {
var Socket = {
alerts: [],
url: location.protocol+'//'+location.hostname+(location.port ? ':'+location.port: ''),
// io is coming from socket.io.js which is coming from Node.js
socket: io.connect(this.url)
};
// set up the listener once
// having this in the controller was creating a
// new listener every time the contoller ran/view loaded
// has to run after Socket is created since it refers to itself
(function() {
Socket.socket.on('get msg', function(data) {
if (data.alert) {
Socket.alerts.push(data.alert);
$rootScope.$digest();
}
});
}());
return Socket;
}])
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.