web-dev-qa-db-fra.com

Améliorer cette usine AngularJS pour l’utiliser avec socket.io

Je veux utiliser socket.io dans AngularJS . J'ai trouvé l'usine 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 que chaque fois que le contrôleur est visité, un autre écouteur est ajouté. Ainsi, lorsqu'un message est reçu, il est traité plusieurs fois.

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

EDIT: Je sais que je ne peux rien restituer à l’usine et y écouter, 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 appelez-le au début de chaque contrôleur utilisant socket.io.

ne se sent toujours pas comme la meilleure solution.

52
Gal Ben-Haim

Supprimez les écouteurs de socket chaque fois que le contrôleur est détruit. Vous devrez lier l’événement $destroy comme ceci:

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 la documentation angularjs .

52
bmleite

Vous pourrez peut-être gérer cela avec un minimum de travail en encapsulant une Scope et en surveillant la diffusion de $destroy; le cas échéant, en ne retirant que les écouteurs ajoutés à la prise. Soyez averti: ce qui suit n'a pas été testé - je le traiterais plus comme un pseudocode que 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) {
     ...
  });
};
8
Michelle Tilley

J'ajouterais un commentaire à la réponse acceptée, mais je ne peux pas. Donc, je vais écrire une réponse ... J'ai le même problème et la réponse la plus simple et la plus simple que j'ai trouvée est celle que vous pouvez trouver ici, sur un autre post , fourni par michaeljoser .

Je vais le copier ci-dessous pour plus de commodité:

Vous devez ajouter removeAllListeners dans votre usine (voir ci-dessous) et disposer du code suivant dans chacun de vos contrôleurs:

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

Usine de prises mise à jour:

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!

5

créer une fonction dans votre service ou usine comme ci-dessous.

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

puis appelez votre contrôleur sous l'événement "$ destroy" comme ci-dessous.

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

c'est résoudre

2
Umair Ahmed

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

J'ai essayé différentes méthodes, mais rien n'a fonctionné comme prévu . Dans mon application, j'utilise une usine socket à la fois dans la MainController et dans une GameController. Lorsque l'utilisateur bascule vers une autre vue, je souhaite uniquement supprimer les événements en double générés par la variable GameController et laisser la variable MainController en cours d'exécution afin que je ne puisse pas utiliser une fonction removeAllListeners. Au lieu de cela, j'ai découvert un meilleur moyen d'éviter simplement de créer des doublons dans mon usine 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
  };
}
1
andreasonny83

J'ai essayé avec le code ci-dessus dans mon AngularApp et trouvé les événements en double . Avec le même exemple de @pootzko en utilisant SocketIoFactory

J'ai ajouté un unSubscribe(even_name) à l'intérieur du $destroy du contrôleur, qui supprimera/effacera le 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>');
    });
}]);
0
Austin Noronha

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

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

Vous pouvez ensuite 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 j'ai cette configuration:

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

}]);
0
RayViljoen

Je le fais pour éviter les doublons et fonctionne assez 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;
  });
},
0
Rafael

En développant la réponse de Brandon ci-dessus, j'ai créé un service qui devrait en outre 1) supprimer les balises angulaires comme. .. '

(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);
0
user65873

J'avais exactement le même problème de doublons après une actualisation du navigateur. J'utilisais une "usine", mais je suis passé à un "service". Voici mon wrapper 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);
                    }
                });
            });
        }
    }

}]);

J'utilise ce service dans mon contrôleur et j'écoute les événements:

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

Une fois que je suis passé d'une usine à un service, je ne reçois plus de doublons après l'actualisation du navigateur.

0
lewma

J'utilise quelque chose comme le code ci-dessous . SocketsService n'est instancié qu'une seule fois et je crois que Angular s'occupe de GC les $ on

Si vous n'aimez pas $ broadcast/$ on, des implémentations Message Bus légèrement plus solides pour Angular sont disponibles ...

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

J'ai résolu ce problème en vérifiant si l'auditeur existe déjà. Si vous avez plusieurs contrôleurs chargés en même temps (pensez à différents modules de page qui utilisent tous socketIO), la suppression de tous les écouteurs enregistrés sur $destroy interférerait avec la fonctionnalité du contrôleur détruit et de tous les contrôleurs encore chargés.

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

Cela pourrait être encore amélioré en recherchant quels écouteurs étaient enregistrés par quel contrôleur et en retirant uniquement les écouteurs appartenant aux contrôleurs détruits pour nettoyer la mémoire.

0
pootzko