Улучшите эту фабрику AngularJS для использования с socket.io
Я хочу использовать socket.io в AngularJS.
Я нашел следующую фабрику:
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: я знаю, что я ничего не могу вернуть на заводе и сделать прослушивание там, затем используйте $rootScope.$ broadcast и $scope.$on в контроллерах, но это не похоже на хорошее решение.
EDIT2: добавлено на завод
init: function() {
socket.removeAllListeners();
}
и вызовите его в начале каждого контроллера, который использует socket.io.
по-прежнему не кажется лучшим решением.
13 ответов:
удалите прослушиватели сокетов при каждом уничтожении контроллера. Вам нужно будет привязать
$destroyсобытие вроде этого:function MyCtrl($scope, socket) { socket.on('message', function(data) { ... }); $scope.$on('$destroy', function (event) { socket.removeAllListeners(); // or something like // socket.removeListener(this); }); };для получения дополнительной информации проверить документация angularjs.
вы могли бы справиться с этим с минимальным количеством работы, обернув область и наблюдая за
$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) { ... }); };
Я хотел добавить комментарий в принятый ответ, но я не могу. Итак, я напишу ответ. У меня была та же проблема, и самый простой и простой ответ, который я нашел, - это тот, который вы можете найти здесь, на другой должности, предоставлен michaeljoser.
я скопирую его ниже для удобства:
вы должны добавить removeAllListeners к вашей фабрике (см. ниже) и иметь следующий код в каждом из ваших контроллеров:
$scope.$on('$destroy', function (event) { socket.removeAllListeners(); });обновление завод сокетов:
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); }); }); } }; });это спасло мой день, я надеюсь, что это будет полезно кому-то еще!
создать функции в сервис или завод, как показано ниже.
unSubscribe: function(listener) { socket.removeAllListeners(listener); }а затем вызовите свой контроллер под событием "$destroy", как показано ниже.
$scope.$on('$destroy', function() { yourServiceName.unSubscribe('eventName'); });это решать
Я только что решил аналогичную проблему, прежде чем прочитать это. Я все это делал на службе.
.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; }])
Я пробовал разные способы, но ничего не получилось, как ожидалось. В моем приложении я использую
socketзавод вMainControllerиGameController. Когда пользователь переключается на другой вид, я хочу только удалить дубликаты событий, созданныхGameControllerи оставитьMainControllerработает, поэтому я не могу использовать
вместо того, чтобы делать приложения.завод, создать сервис (синглтон) вот так:
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 }]);
расширяя ответ Брэндона выше, я создал сервис, который должен дополнительно 1) удалять угловые теги, такие как .$$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);
Я использую что-то вроде ниже код. socketsService создается только один раз, и я считаю, что Angular заботится о GC the $on's
Если вам не нравится $broadcast/$on, есть несколько более твердых реализаций шины сообщений для 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) { //... }]);
Я решил эту проблему, проверив, существует ли уже прослушиватель. Если у вас есть несколько контроллеров, которые загружаются одновременно (подумайте о разных модулях страниц, которые все используют 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); } }); }) } }; });Это может быть дополнительно улучшено путем отслеживания того, какие слушатели были зарегистрированы каким контроллером и удаления только слушатели, которые принадлежат к уничтоженным контроллерам, чтобы очистить память.
Я делаю это, чтобы избежать повторения слушателей и работает довольно хорошо.
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; }); },
у меня была точно такая же проблема повторяющихся событий после обновления браузера. Я использовал "фабрику", но переключился на использование "службы". Вот мой 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 ); } }]);Как только я переключился с использования фабрики на использование службы, я не получаю дубликатов после обновления браузера.
Я попытался с вышеуказанным кодом в моем AngularApp и обнаружил, что события дублируются. С тем же примером из @pootzko с помощью SocketIoFactory
я добавил
unSubscribe(even_name)внутри$destroyконтроллера, который будет удалить/очистить socketEventListnervar 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>'); }); }]);
Comments