Mejore esta fábrica AngularJS para usar con socket.io



(9)

Acabo de resolver un problema similar antes de leer esto. Lo hice todo en el Servicio.

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

Quiero usar socket.io en AngularJS. Encontré la siguiente fábrica:

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

y se usa en el controlador como:

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

el problema es que cada vez que se visita el controlador se agrega otro oyente, por lo que cuando se recibe un mensaje, se maneja varias veces.

¿Cuál puede ser una mejor estrategia para integrar socket.io con AngularJS?

EDITAR: Sé que no puedo devolver nada en la fábrica y escuchar allí, luego uso $ rootScope. $ Broadcast y $ scope. $ On en los controladores, pero no parece una buena solución.

EDIT2: agregado a la fábrica

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

y llámalo al comienzo de cada controlador que use socket.io.

todavía no se siente como la mejor solución.


Agregaría un comentario a la respuesta aceptada, pero no puedo. Entonces, escribiré una respuesta. Tuve el mismo problema y la respuesta más fácil y simple que encontré es la que puedes encontrar aquí, en otra publicación , proporcionada por michaeljoser .

Lo copiaré a continuación por conveniencia:

Debe agregar removeAllListeners a su fábrica (ver a continuación) y tener el siguiente código en cada uno de sus controladores:

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

Fábrica de socket actualizada:

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

Me salvó el día, ¡espero que sea útil para otra persona!


Elimine los detectores de socket cada vez que se destruya el controlador. Deberá enlazar el evento $destroy esta manera:

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

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

Para más información, consulte la documentación de angularjs .


En lugar de hacer app.factory, crea un service (singleton) como ese:

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

Luego puede simplemente insertar el servicio en su aplicación y usarlo en controladores como lo haría con $ rootScope.

Aquí hay un ejemplo más completo de cómo tengo esta configuración:

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

}]);

Estaba teniendo exactamente el mismo problema de eventos duplicados luego de una actualización del navegador. Estaba usando una 'fábrica', pero cambié para usar un 'servicio'. Aquí está mi contenedor 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);
                    }
                });
            });
        }
    }

}]);

Uso este servicio dentro de mi controlador y escucho eventos:

myApp.controller('myController', ['mysocketio', function(mysocketio)
{
    mysocketio.on( 'myevent', function(msg)
    {
        console.log('received event: ' + msg );
    }
}]);

Una vez que cambié de usar una fábrica a usar un servicio, no recibo duplicados después de actualizar el navegador.


Estoy haciendo esto para evitar oyentes duplicados y funciona bastante bien.

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

Probé de diferentes maneras, pero nada funcionó como se esperaba. En mi aplicación, estoy usando una fábrica de socket tanto en MainController como en GameController . Cuando el usuario cambia a una vista diferente, solo quiero eliminar los eventos duplicados generados por GameController y dejar el MainController ejecución, por lo que no puedo usar la función removeAllListeners . En cambio, descubrí una forma mejor de evitar la creación de duplicados dentro de mi fábrica de socket :

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

Resolví este problema comprobando si el oyente ya existe. Si tiene varios controladores que están cargados al mismo tiempo (piense en diferentes módulos de página que utilizan socketIO), eliminar todos los detectores registrados en $destroy rompería la funcionalidad tanto del controlador destruido como de todos los controladores que todavía están cargados.

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

Esto podría mejorarse aún más rastreando qué oyentes fueron registrados por cada controlador y eliminando solo los oyentes que pertenecen a los controladores destruidos para limpiar la memoria.


crear una función en su servicio o fábrica como a continuación.

unSubscribe: function(listener) {
    socket.removeAllListeners(listener);
}

y luego llama a tu controlador bajo el evento "$ destroy" como se muestra a continuación.

$scope.$on('$destroy', function() {
    yourServiceName.unSubscribe('eventName');
});

eso es resolver





socket.io