Улучшите этот AngularJS factory для использования с socket.io
Я хочу использовать socket.io в AngularJS.
Я нашел следующий 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);
}
});
})
}
};
и он используется в контроллере, например:
function MyCtrl($scope, socket) {
socket.on('message', function(data) {
...
});
};
проблема заключается в том, что каждый раз, когда контроллер посещается, другой слушатель добавляется, поэтому, когда сообщение получено, оно обрабатывается несколько раз.
что может быть лучшей стратегией для интеграции socket.io с AngularJS?
EDIT: я знаю, что я ничего не могу вернуть в factory и прослушивать там, а затем использовать $rootScope. $broadcast и $scope. $on в контроллерах, но это не похоже на хорошее решение.
EDIT2: добавлен в factory
init: function() {
socket.removeAllListeners();
}
и вызовите его в начале каждого контроллера, использующего socket.io.
по-прежнему не похоже на лучшее решение.
Ответы
Ответ 1
Удалите прослушиватели сокетов всякий раз, когда контроллер уничтожается.
Вам нужно будет связать событие $destroy
следующим образом:
function MyCtrl($scope, socket) {
socket.on('message', function(data) {
...
});
$scope.$on('$destroy', function (event) {
socket.removeAllListeners();
// or something like
// socket.removeListener(this);
});
};
Для получения дополнительной информации просмотрите документацию angularjs.
Ответ 2
Возможно, вы справитесь с этим с минимальным объемом работы, включив область видимости и наблюдая за тем, как $destroy
будет транслироваться, и когда это произойдет, только удаление из сокета прослушивателей, которые были добавлены в контексте это область. Будьте осторожны: все, что следует, не было проверено - я бы рассматривал его скорее как псевдокод, чем фактический код.:)
// 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) {
...
});
};
Ответ 3
Я бы добавил комментарий к принятому ответу, но я не могу. Итак, я напишу ответ.
У меня была такая же проблема, и самый простой и простой ответ, который я нашел, - это тот, который вы можете найти здесь, в другом сообщении, предоставленном michaeljoser.
Я скопирую его ниже для удобства:
Вы должны добавить removeAllListeners в свой factory (см. ниже) и иметь следующий код на каждом из ваших контроллеров:
$scope.$on('$destroy', function (event) {
socket.removeAllListeners();
});
Обновлен сокет 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);
});
});
}
};
});
Это спасло мой день, я надеюсь, что это будет полезно кому-то еще!
Ответ 4
создать функцию в вашей службе или factory, как показано ниже.
unSubscribe: function(listener) {
socket.removeAllListeners(listener);
}
а затем вызовите контроллер в разделе "$ destroy", как показано ниже.
$scope.$on('$destroy', function() {
yourServiceName.unSubscribe('eventName');
});
которые решают
Ответ 5
Я только что решил аналогичную проблему, прежде чем я прочитал это. Я сделал все это в Службе.
.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;
}])
Ответ 6
Я пробовал разные способы, но ничего не работало, как ожидалось.
В моем приложении я использую socket
factory как в MainController
, так и в GameController
. Когда пользователь переключается на другое представление, я хочу только удалить повторяющиеся события, сгенерированные GameController
, и оставить MainController
запущенным, поэтому я не могу использовать функцию removeAllListeners
. Вместо этого я обнаружил лучший способ избежать создания дубликатов внутри моего 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
};
}
Ответ 7
Вместо того, чтобы делать app.factory, создайте service (singleton) следующим образом:
var service = angular.module('socketService', []);
service.factory('$socket', function() {
// Your factory logic
});
Затем вы можете просто добавить службу в свое приложение и использовать ее в контроллерах, как и $rootScope.
Вот более полный пример того, как я настроил это:
// 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
}]);
Ответ 8
Развернувшись на ответе Брэндона выше, я создал службу, которая должна дополнительно 1) разделять теги angular как. $$ hashKey, который остается на элементах, и 2) позволяет использовать имена сокетов, таких как socketsof ('..').он ( '..'
(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);
Ответ 9
Я использую что-то вроде кода ниже.
socketsService создается только один раз, и я полагаю, что Angular заботится о GC $on
Если вам не нравится $broadcast/$on, для Angular доступны несколько более твердых реализаций Message Bus...
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) {
//...
}]);
Ответ 10
Я решил эту проблему, проверив, существует ли уже прослушиватель. Если у вас есть несколько контроллеров, которые все загружаются одновременно (подумайте о разных модулях страниц, которые все используют socketIO), удаление всех зарегистрированных прослушивателей на $destroy
приведет к поломке функциональности как уничтоженного контроллера, так и всех контроллеров, которые все еще загружены.
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);
}
});
})
}
};
});
Это может быть дополнительно улучшено путем отслеживания того, какие слушатели были зарегистрированы с помощью контроллера и удаления только прослушивателей, принадлежащих уничтоженным контроллерам, для очистки памяти.
Ответ 11
Я делаю это, чтобы избежать дублирования слушателей и работает очень хорошо.
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;
});
},
Ответ 12
У меня возникла такая же проблема с повторяющимися событиями после обновления браузера. Я использовал "factory", но переключился на использование "службы". Здесь моя оболочка socket.io:
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);
}
});
});
}
}
}]);
Я использую эту службу внутри своего контроллера и слушаю события:
myApp.controller('myController', ['mysocketio', function(mysocketio)
{
mysocketio.on( 'myevent', function(msg)
{
console.log('received event: ' + msg );
}
}]);
Как только я переключился с использования factory на использование службы, я не получаю дубликаты после обновления браузера.
Ответ 13
Я попробовал с вышеуказанным кодом в своем AngularApp и нашел дубликаты событий.
В том же примере из @pootzko, используя SocketIoFactory
Я добавил unSubscribe(even_name)
внутри $destroy
контроллера, который удалит/очистит 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>');
});
}]);