Sharing Top Content from the Angular-sphere.

[SOLVED] Improve this AngularJS factory to use with socket.io- Angular.js Recipes

  • Instead I discovered a better way to just avoid creating duplicates inside my 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 callback); } function emit(eventName, data, callback) { socket.emit(eventName, data, 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() { 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.
  • $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.

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.


@angular_recipe: Improve this AngularJS factory to use with socket.io #Angular #AngularJS

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

and it is used in the controller like:

function MyCtrl($scope, socket) { socket.on(‘message’, function(data) { … }); };

the problem is that each time the controller is visited another listener is added, so when a message is received it is handled multiple times.

what can be a better strategy to integrate socket.io with AngularJS ?

EDIT: I know that I can return nothing in the factory and do the listening there, then use $rootScope.$broadcast and $scope.$on in the controllers, but it doesn’t look like a good solution.

EDIT2: added to the factory

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

still doesn’t feel like the best solution.

event like this:

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; }])

I tried with the above code in my AngularApp and found the events duplicating. With the same Example from @pootzko using the SocketIoFactory

of the Controller, that will remove/clear the 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(‘‘, function (data) { }); //On destroy remove the eventListner on socketConnection $scope.$on(‘$destroy’, function (event) { console.log(‘[homeControl] destroy…’); SocketIoFactory.unSubscribe(‘‘); }); }]);

Instead of doing app.factory, create a service (singleton) like so:

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

You can then simply inject the service to your app and use it in controllers as you would $rootScope.

Here is a more complete example of how I have this set up:

// 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 }]);

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; }); },

Expanding on Brandon’s answer above, I’ve created a service that should additionally 1) strip angular tags like .$$hashKey that gets left on elements, and 2) allows for namespaced sockets like socketsof(‘..’).on(‘..’

(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); 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 }; } create function in your service or factory like below. unSubscribe: function(listener) { socket.removeAllListeners(listener); } and then call in your controller under the “$destroy” event like below. $scope.$on(‘$destroy’, function() { yourServiceName.unSubscribe(‘eventName’); }); that’s solve I was having the exact same problem of duplicate events after a browser refresh. I was using a ‘factory’, but switched to use a ‘service’. Here’s my socket.io wrapper: 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 ); } }]); Once I switched from using a factory to using a service, I don’t receive duplicates after a browser refresh. I use something like the code below. socketsService is only instantiated once and I believe Angular takes care of GC the $on’s If you don’t like $broadcast/$on, there are some slightly more solid Message Bus implementations for Angular available… 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) { //… }]); 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. 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. 🙂 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! Angular.js Recipes are structured in a Cookbook format featuring recipes that contain problem statements and solutions. A detailed explanation follows each problem statement of the recipe. This is usually contained within the solution; however, an optional discussion section can often contain other useful information helping to demonstrate how the solution works. Angular.js is a JavaScript-based open-source front-end web application framework mainly maintained by Google and by a community of individuals and corporations to address many of the challenges encountered in developing single-page applications. The JavaScript components complement Apache Cordova, the framework used for developing cross-platform mobile apps. It aims to simplify both the development and the testing of such applications by providing a framework for client-side model–view–controller (MVC) and model–view–viewmodel (MVVM) architectures, along with components commonly used in rich Internet applications. Lost? Begin by working through Angular’s Getting Started Guide to get yourself up-and-running.

[SOLVED] Improve this AngularJS factory to use with socket.io- Angular.js Recipes

Comments are closed, but trackbacks and pingbacks are open.