52

我想在 AngularJS 中使用 socket.io。我找到了以下工厂:

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 集成的更好策略是什么?

编辑:我知道我不能在工厂返回任何内容并在那里进行监听,然后在控制器中使用 $rootScope.$broadcast 和 $scope.$on ,但这看起来不是一个好的解决方案。

EDIT2:添加到工厂

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

并在每个使用 socket.io 的控制器的开头调用它。

仍然感觉不是最好的解决方案。

4

13 回答 13

52

每当控制器被销毁时删除套接字侦听器。您将需要$destroy像这样绑定事件:

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

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

有关更多信息,请查看angularjs 文档

于 2013-01-17T23:26:12.793 回答
8

您可以通过包装一个 Scope 并监视要广播的工作量来处理这个问题$destroy,并且当它是时,只从套接字中删除在该 Scope 的上下文中添加的侦听器。请注意:以下内容尚未经过测试——我将其视为伪代码而不是实际代码。:)

// 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) {
     ...
  });
};
于 2013-01-18T06:29:15.557 回答
5

我会在接受的答案中添加评论,但我不能。所以,我会写一个回复。我遇到了同样的问题,我找到的最简单最简单的答案是您可以在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);
              });
          }); 
      }
    };
});

它拯救了我的一天,我希望它对其他人有用!

于 2014-05-23T13:48:53.460 回答
2

在您的服务或工厂中创建功能,如下所示。

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

然后在“$destroy”事件下调用你的控制器,如下所示。

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

那解决了

于 2016-02-22T17:28:38.313 回答
1

在阅读本文之前,我刚刚解决了一个类似的问题。我在服务中完成了这一切。

.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;
}])
于 2014-06-06T20:48:08.843 回答
1

我尝试了不同的方法,但没有按预期工作。在我的应用程序中,我socket在. 当用户切换到不同的视图时,我只想删除由 生成的重复事件并保持运行,因此我无法使用函数。相反,我发现了一种更好的方法来避免在我的工厂内创建重复项:MainControllerGameControllerGameControllerMainControllerremoveAllListenerssocket

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
  };
}
于 2016-01-10T12:46:17.507 回答
0

扩展上面 Brandon 的回答,我创建了一个服务,该服务还应该 1)剥离像 .$$hashKey 这样留在元素上的角度标签,2)允许像 socketsof('..').on(' 这样的命名空间套接字..'

(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);
于 2013-04-19T01:29:29.100 回答
0

我通过检查监听器是否已经存在来解决这个问题。如果您有多个同时加载的控制器(想想都使用 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);
                    }
                });
            })
        }
    };
});

这可以通过跟踪哪个控制器注册了哪些侦听器并仅删除属于已销毁控制器的侦听器以清理内存来进一步改进。

于 2014-08-15T17:22:46.167 回答
0

我使用类似下面的代码。socketsService 只实例化一次,我相信 Angular 会处理 $on 的 GC

如果您不喜欢 $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) {
        //...
    }]);
于 2013-08-09T21:28:49.310 回答
0

不要做 app.factory,而是像这样创建一个服务(单例):

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

}]);
于 2013-01-17T23:06:05.083 回答
0

我在 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>');
    });
}]);
于 2017-06-22T13:23:23.433 回答
0

我这样做是为了避免重复的听众并且效果很好。

 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;
  });
},
于 2015-07-22T00:47:48.607 回答
0

浏览器刷新后,我遇到了完全相同的重复事件问题。我使用的是“工厂”,但改用了“服务”。这是我的 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 );
    }
}]);

一旦我从使用工厂切换到使用服务,在浏览器刷新后我不会收到重复。

于 2015-09-02T20:52:19.563 回答