Improve this AngularJS factory for use with socket.io

I want to use socket.io in AngularJS. I found the following factory:

app.factory('socket', function ($rootScope) { var socket = io.connect(); 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); } }); }) } }; 

and it is used in the controller, for example:

 function MyCtrl($scope, socket) { socket.on('message', function(data) { ... }); }; 

the problem is that every time the controller is visited, another listener is added, so when the message is received, it is processed several times.

What could be the best strategy for integrating socket.io with AngularJS?

EDIT: I know that I cannot return anything to the factory and listen there, and then use $ rootScope. $ broadcast and $ scope. $ on in controllers, but that doesn't seem like a good solution.

EDIT2: added to factory

 init: function() { socket.removeAllListeners(); } 

and call it at the beginning of each controller using socket.io.

still not like the best solution.

+51
angularjs
Jan 17 '13 at 22:10
source share
13 answers

Remove socket listeners whenever the controller is destroyed. You will need to bind the $destroy event as follows:

 function MyCtrl($scope, socket) { socket.on('message', function(data) { ... }); $scope.$on('$destroy', function (event) { socket.removeAllListeners(); // or something like // socket.removeListener(this); }); }; 

See the angularjs documentation for more information.

+52
Jan 17 '13 at 23:26
source share

Perhaps you can do this with a minimum amount of work by turning on the scope and observing how $destroy will be broadcast, and when this happens, only removing listeners from the socket that were added in the context of this area. Be careful: everything that follows has not been verified - I would consider it more like a pseudo-code than the 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) { ... }); }; 
+8
Jan 18 '13 at 6:29
source share

I would add a comment to the accepted answer, but I cannot. So, I will write the answer. I had the same issue, and the simplest and easiest answer I found is the one you can find here in another post provided by michaeljoser .

I will copy it below for convenience:

You must add removeAllListeners to your factory (see below) and have the following code on each of your controllers:

 $scope.$on('$destroy', function (event) { socket.removeAllListeners(); }); 

Updated factory socket:

 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!

+5
May 23 '14 at 13:48
source share

create a function in your service or factory as shown below.

 unSubscribe: function(listener) { socket.removeAllListeners(listener); } 

and then invoke the controller in the $ destroy section as shown below.

 $scope.$on('$destroy', function() { yourServiceName.unSubscribe('eventName'); }); 

who decide

+2
Feb 22 '16 at 17:28
source share

I just solved a similar problem before I read this. I did all this 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; }]) 
+1
Jun 06 '14 at 20:48
source share

I tried different ways, but nothing worked as expected. In my application, I use socket factory in both MainController and GameController . When the user switches to a different view, I only want to remove the repeating events generated by the GameController and leave the MainController running, so I cannot use the removeAllListeners function. Instead, I found a better way to avoid creating duplicates inside my socket factory:

 app.factory('socket', function ($rootScope) { var socket = io.connect(); function on(eventName, callback) { socket.on(eventName, function () { var args = arguments; $rootScope.$apply(function () { callback.apply(socket, args); }); }); // Remove duplicate listeners socket.removeListener(eventName, callback); } function emit(eventName, data, callback) { socket.emit(eventName, data, function () { var args = arguments; $rootScope.$apply(function () { if (callback) { callback.apply(socket, args); } }); }); // Remove duplicate listeners socket.removeListener(eventName, callback); } return { on: on, emit: emit }; } 
+1
Jan 10 '16 at 12:46 on
source share

Instead of doing app.factory, create a service (singleton) as follows:

 var service = angular.module('socketService', []); service.factory('$socket', function() { // Your factory logic }); 

Then you can simply add the service to your application and use it in controllers, like $ rootScope.

Here is a more complete example of how I configured this:

 // App module var app = angular.module('app', ['app.services']); // services var services = angular.module('app.services', []); // Socket service services.factory('$socket', ['$rootScope', function(rootScope) { // Factory logic here }]); // Controller app.controller('someController', ['$scope', '$socket', function(scope, socket) { // Controller logic here }]); 
0
Jan 17 '13 at 23:06
source share

Turning around Brandon's answer above, I created a service that should additionally 1) separate angular tags as. $$ hashKey, which stays on the elements, and 2) allows the use of socket names such as socketsof ('..'). it ('..'

 (function (window, app, undefined) { 'use strict'; var ScopedSocket = function (socket, $rootScope) { this.socket = socket; this.$rootScope = $rootScope; this.listeners = []; this.childSockets = []; }; ScopedSocket.prototype.removeAllListeners = function () { var i; for (i = 0; i < this.listeners.length; i++) { var details = this.listeners[i]; this.socket.removeListener(details.event, details.fn); } for (i = 0; i < this.childSockets.length; i++) { this.childSockets[i].removeAllListeners(); } }; ScopedSocket.prototype.on = function (event, callback) { var socket = this.socket; var $rootScope = this.$rootScope; this.listeners.push({event: event, fn: callback}); socket.on(event, function () { var args = arguments; $rootScope.$apply(function () { callback.apply(socket, args); }); }); }; ScopedSocket.prototype.emit = function (event, data, callback) { var socket = this.socket; var $rootScope = this.$rootScope; socket.emit(event, angular.fromJson(angular.toJson(data)), function () { var args = arguments; $rootScope.$apply(function () { if (callback) { callback.apply(socket, args); } }); }); }; ScopedSocket.prototype.of = function (channel) { var childSocket = new ScopedSocket(this.socket.of(channel), this.$rootScope); this.childSockets.push(childSocket); return childSocket; }; app.factory('Socket', ['$rootScope', function ($rootScope) { var socket = $rootScope.socket; return function(scope) { var scopedSocket = new ScopedSocket(socket, $rootScope); scope.$on('$destroy', function() { scopedSocket.removeAllListeners(); }); return scopedSocket; }; }]); })(window, window.app); 
0
Apr 19 '13 at 1:29
source share

I am using something like the code below. socketsService is created only once, and I suppose Angular takes care of GC $ on

If you don't like $ broadcast / $ on, several more solid Message Bus implementations are available for Angular ...

 app.service('socketsService', ['$rootScope', function ($rootScope) { var socket = window.io.connect(); socket.on('info', function(data) { $rootScope.$broadcast("info_received", data); }); socket.emit('ready', "Hello"); }]); app.controller("infoController",['$scope', function ($scope) { $scope.$root.$on("info_received", function(e,data){ console.log(data); }); //... }]); app.run( ['socketsService', function (socketsService) { //... }]); 
0
Aug 09 '13 at 21:28
source share

I solved this problem by checking if a listener already exists. If you have several controllers that all load at the same time (think of different page modules that all use socketIO), deleting all registered listeners on $destroy will 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 can be further improved by keeping track of which listeners have been registered with the controller and removing only the listeners belonging to the destroyed controllers to clear the memory.

0
Aug 15 '14 at 17:22
source share

I do this to avoid duplicate listeners and works very 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; }); }, 
0
Jul 22 '15 at 0:47
source share

I had the same issue with recurring events after updating the browser. I used the "factory", but switched to using the "service". Here is my socket.io shell:

 myApp.service('mysocketio',['$rootScope', function($rootScope) { var socket = io.connect(); 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); } }); }); } } }]); 

I use this service inside my controller and listen for events:

 myApp.controller('myController', ['mysocketio', function(mysocketio) { mysocketio.on( 'myevent', function(msg) { console.log('received event: ' + msg ); } }]); 

As soon as I switched from using factory to using a service, I don't get duplicates after updating the browser.

0
Sep 02 '15 at 20:52
source share

I tried with the above code in my AngularApp and found duplicate events. In the same example from @pootzko using SocketIoFactory

I added unSubscribe(even_name) inside the $destroy controller, which will remove / clear socketEventListner

 var app = angular.module("app", []); .. .. .. //Create a SocketIoFactory app.service('SocketIoFactory', function($rootScope){ console.log("SocketIoFactory...."); //Creating connection with server var protocol = 'ws:',//window.location.protocol, host = window.location.host, port = 80, socket = null; var nodePath = protocol+'//'+host+':'+port+'/'; function listenerExists(eventName) { return socket.hasOwnProperty("$events") && socket.$events.hasOwnProperty(eventName); } return { connect: function () { socket = io.connect(nodePath); console.log('SOCKET CONNECTION ... ',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); } }); }) }, unSubscribe: function(listener) { socket.removeAllListeners(listener); } }; }); .. .. .. //Use in a controller app.controller("homeControl", ['$scope', 'SocketIoFactory', function ($scope, SocketIoFactory) { //Bind the events SocketIoFactory.on('<event_name>', function (data) { }); //On destroy remove the eventListner on socketConnection $scope.$on('$destroy', function (event) { console.log('[homeControl] destroy...'); SocketIoFactory.unSubscribe('<event_name>'); }); }]); 
0
Jun 22 '17 at 13:23
source share



All Articles