web-dev-qa-db-ja.com

socket.ioで使用するこのAngularJSファクトリーを改善する

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を使用する各コントローラーの先頭で呼び出します。

それでも最良の解決策のようには感じません。

52
Gal Ben-Haim

コントローラーが破棄されるたびに、ソケットリスナーを削除します。次のように$destroyイベントをバインドする必要があります。

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

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

詳細については、 angularjs documentation を確認してください。

52
bmleite

Scopeをラップし、$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) {
     ...
  });
};
8
Michelle Tilley

受け入れられた答えにコメントを追加しますが、できません。だから、返信を書きます。私は同じ問題を抱えていましたが、私が見つけた最も簡単で簡単な答えは、 ここでは別の投稿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);
              });
          }); 
      }
    };
});

それは私の一日を救った、私はそれが他の誰かに役立つことを願っています!

5

サービスまたはファクトリで関数を作成します。以下のようになります。

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

次に、以下のような「$ destroy」イベントの下でコントローラーを呼び出します。

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

それは解決です

2
Umair Ahmed

これを読む前に、同様の問題を解決しました。私はそれをすべてサービスでやった。

.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

さまざまな方法を試しましたが、期待どおりに機能しませんでした。私のアプリでは、socketMainControllerの両方でGameControllerファクトリーを使用しています。ユーザーが別のビューに切り替えたとき、GameControllerによって生成された重複イベントのみを削除し、MainControllerを実行したままにして、removeAllListeners関数を使用できないようにします。代わりに、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

AngularAppで上記のコードを試したところ、イベントが重複していることがわかりました。 SocketIoFactoryを使用した@pootzkoの同じ例

コントローラーの_$destroy_内にunSubscribe(even_name)を追加しました。これは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

App.factoryを行う代わりに、次のように service (シングルトン)を作成します。

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

}]);
0
RayViljoen

私はこれをリスナーの重複を避けるために行っており、かなりうまく機能しています。

 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

上記のBrandonの答えを拡張して、1)strip 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);
0
user65873

ブラウザを更新した後、イベントが重複するというまったく同じ問題がありました。 「工場」を使用していましたが、「サービス」を使用するように切り替えました。これが私の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 );
    }
}]);

ファクトリーの使用からサービスの使用に切り替えた後、ブラウザーを更新した後、重複を受信しません。

0
lewma

以下のコードのようなものを使用します。 socketsServiceは1回だけインスタンス化され、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) {
        //...
    }]);
0
malix

リスナーがすでに存在するかどうかを確認することで、この問題を解決しました。すべて同時に読み込まれる複数のコントローラーがある場合(すべてが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);
                    }
                });
            })
        }
    };
});

これは、どのコントローラーがどのリスナーを登録したかを追跡し、破壊されたコントローラーに属するリスナーのみを削除してメモリをクリーンアップすることでさらに改善できます。

0
pootzko