Подтвердить что ты не робот

Улучшите этот 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.

по-прежнему не похоже на лучшее решение.

4b9b3361

Ответ 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>');
    });
}]);