52 votes

Améliorer cette fabrique AngularJS pour l'utiliser avec socket.io

Je veux utiliser socket.io dans AngularJS. J'ai trouvé la fabrique suivante :

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

et il est utilisé dans le contrôleur comme :

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

Le problème est qu'à chaque fois que le contrôleur est visité, un autre écouteur est ajouté, de sorte que lorsqu'un message est reçu, il est traité plusieurs fois.

quelle peut être une meilleure stratégie pour intégrer socket.io avec AngularJS ?

EDIT : Je sais que je peux ne rien retourner dans la factory et faire l'écoute à cet endroit, puis utiliser $rootScope.$broadcast et $scope.$on dans les contrôleurs, mais cela ne semble pas être une bonne solution.

EDIT2 : ajouté à l'usine

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

et l'appeler au début de chaque contrôleur qui utilise socket.io.

ne semble toujours pas être la meilleure solution.

52voto

bmleite Points 11359

Supprimez les écouteurs de socket chaque fois que le contrôleur est détruit. Vous devrez lier le $destroy un événement comme celui-ci :

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

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

Pour plus d'informations, consultez le site documentation sur angularjs .

8voto

Brandon Tilley Points 49142

Vous pourriez être en mesure de gérer cela avec un minimum de travail en emballant un Scope et en regardant pour $destroy pour être diffusé, et lorsqu'il l'est, ne retirer de la socket que les listeners qui ont été ajoutés dans le contexte de ce Scope. Attention : ce qui suit n'a pas été testé - je le considère plus comme un pseudo-code que comme du code réel :)

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

5voto

Je voudrais ajouter un commentaire à la réponse acceptée, mais je ne peux pas. Je vais donc écrire une réponse. J'ai eu le même problème et la réponse la plus facile et la plus simple que j'ai trouvée est la suivante ici, sur un autre poste fourni par michaeljoser .

Je la copie ci-dessous pour plus de commodité :

Vous devez ajouter removeAllListeners à votre factory (voir ci-dessous) et avoir le code suivant dans chacun de vos contrôleurs :

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

Mise à jour de la fabrique de prises :

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

Cela a sauvé ma journée, j'espère que cela sera utile à quelqu'un d'autre !

1voto

Jason Points 1040

Je viens de résoudre un problème similaire avant de lire ceci. J'ai tout fait dans le 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;
}])

0voto

RayViljoen Points 418

Au lieu de faire app.factory, créez un service (singleton) comme ça :

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

Vous pouvez alors simplement injecter le service dans votre application et l'utiliser dans les contrôleurs comme vous le feriez avec $rootScope.

Voici un exemple plus complet de la façon dont je l'ai configuré :

// 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

}]);

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X