使用ngResource,socket.io和$ q

时间:2015-07-09 17:56:41

标签: javascript angularjs socket.io angular-promise ngresource

我正在尝试创建一个AngularJS工厂,它通过从API检索初始项目然后监听套接字更新来自动维护资源集合,以使集合保持最新状态。

angular.module("myApp").factory("myRESTFactory", function (Resource, Socket, ErrorHandler, Confirm, $mdToast, $q, $rootScope) {

  var Factory = {};

  // Resource is the ngResource that fetches from the API
  // Factory.collection is where we'll store the items
  Factory.collection = Resource.query();

  // manually add something to the collection
  Factory.push = function(item) {
    Factory.collection.push(item);
  };

  // search the collection for matching objects
  Factory.find = function(opts) {
    return $q(function(resolve, reject) {
      Factory.collection.$promise.then(function(collection){
        resolve(_.where(Factory.collection, opts || {}));
      });
    });
  };

  // search the collection for a matching object
  Factory.findOne = function(opts) {
    return $q(function(resolve, reject) {
      Factory.collection.$promise.then(function(collection){

        var item = _.findWhere(collection, opts || {});

        idx = _.findIndex(Factory.collection, function(u) {
          return u._id === item._id;
        });
        resolve(Factory.collection[idx]);
      });
    });
  };

  // create a new item; save to API & collection
  Factory.create = function(opts) {
    return $q(function(resolve, reject) {
      Factory.collection.$promise.then(function(collection){
        Resource.save(opts).$promise.then(function(item){
          Factory.collection.push(item);
          resolve(item);
        });
      });
    });
  };

  Factory.update = function(item) {
    return $q(function(resolve, reject) {
      Factory.collection.$promise.then(function(collection){
        Resource.update({_id: item._id}, item).$promise.then(function(item) {
          var idx = _.findIndex(collection, function(u) {
            return u._id === item._id;
          });
          Factory.collection[idx] = item;
          resolve(item);
        });
      });
    });
  };

  Factory.delete = function(item) {
    return $q(function(resolve, reject) {
      Factory.collection.$promise.then(function(collection){
        Resource.delete({_id: item._id}, item).$promise.then(function(item) {
          var idx = _.findIndex(collection, function(u) {
            return u._id === item._id;
          });

          Factory.collection.splice(idx, 1);
          resolve(item);
        });
      });
    });
  };

  // new items received from the wire
  Socket.on('new', function(item){
    idx = _.findIndex(Factory.collection, function(u) {
      return u._id === item._id;
    });
    if(idx===-1) Factory.collection.push(item);

    // this doesn't help
    $rootScope.$apply();
  });

  Socket.on('update', function(item) {

    idx = _.findIndex(Factory.collection, function(u) {
      return u._id === item._id;
    });

    Factory.collection[idx] = item;

    // this doesn't help
    $rootScope.$apply();

  });

  Socket.on('delete', function(item) {

    idx = _.findIndex(Factory.collection, function(u) {
      return u._id === item._id;
    });

    if(idx!==-1) Factory.collection.splice(idx, 1);

  });

  return Factory;

});

我的后端很稳固,套接字消息正确通过。但是,如果使用任何Factory方法,控制器不会对集合的更新做出响应。

这有效(响应集合的套接字更新):

$scope.users = User.collection;

这不起作用(它最初加载用户但不知道对集合的更新):

User.findOne({ _id: $routeParams.user_id }).then(function(user){
  $scope.user = user;
});

如何让我的控制器响应对集合更改的更新?

更新

我能够通过更改它来实现控制器中的变通方法:

if($routeParams.user_id) {
  User.findOne({ _id: $routeParams.user_id }).then(function(user){
    $scope.user = user;
  });
}

对此:

$scope.$watchCollection('users', function() {
  if($routeParams.user_id) {
    User.findOne({ _id: $routeParams.user_id }).then(function(user){
      $scope.user = user;
    });
  }
});

但是,没有人喜欢变通方法,特别是当它涉及控制器中的冗余代码时。我正在为能在工厂内解决这个问题的人提出一个问题的赏金。

1 个答案:

答案 0 :(得分:2)

解决方案是让工厂方法返回一个空对象/数组以便稍后填充(类似于ngResource的工作方式)。然后将套接字侦听器附加到那些返回对象/数组和主Factory.collection数组。

angular.module("myApp").factory("myRESTFactory",
  function (Resource, Socket, ErrorHandler, Confirm, $mdToast, $q) {

  var Factory = {};

  // Resource is the ngResource that fetches from the API
  // Factory.collection is where we'll store the items
  Factory.collection = Resource.query();

  // This function attaches socket listeners to given array
  // or object and automatically updates it based on updates
  // from the websocket
  var socketify = function(thing, opts){

    // if attaching to array
    // i.e. myRESTFactory.find({name: "John"})
    // was used, returning an array
    if(angular.isArray(thing)) {

      Socket.on('new', function(item){

        // push the object to the array only if it
        // matches the query object
        var matches = $filter('find')([item], opts);

        if(matches.length){
          var idx = _.findIndex(thing, function(u) {
            return u._id === item._id;
          });
          if(idx===-1) thing.push(item);
        }
      });

      Socket.on('update', function(item) {
        var idx = _.findIndex(thing, function(u) {
          return u._id === item._id;
        });

        var matches = $filter('find')([item], opts);

        // if the object matches the query obj,
        if(matches.length){

          // and is already in the array
          if(idx > -1){

            // then update it
            thing[idx] = item;

          // otherwise
          } else {

            // add it to the array
            thing.push(item);
          }

        // if the object doesn't match the query
        // object anymore,
        } else {

          // and is currently in the array
          if(idx > -1){

            // then splice it out
            thing.splice(idx, 1);
          }
        }
      });

      Socket.on('delete', function(item) {

        ...

      });

    // if attaching to object
    // i.e. myRESTFactory.findOne({name: "John"})
    // was used, returning an object
    } else if (angular.isObject(thing)) {

      Socket.on('update', function(item) {
        ...
      });

      Socket.on('delete', function(item) {
        ...
      });

    }

    // attach the socket listeners to the factory
    // collection so it is automatically maintained
    // by updates from socket.io
    socketify(Factory.collection);

    // return an array of results that match
    // the query object, opts
    Factory.find = function(opts) {

      // an empty array to hold matching results
      var results = [];

      // once the API responds,
      Factory.collection.$promise.then(function(){

        // see which items match
        var matches = $filter('find')(Factory.collection, opts);

        // and add them to the results array
        for(var i = matches.length - 1; i >= 0; i--) {
          results.push(matches[i]);
        }
      });

      // attach socket listeners to the results
      // array so that it is automatically maintained
      socketify(results, opts);

      // return results now. initially it is empty, but
      // it will be populated with the matches once
      // the api responds, as well as pushed, spliced,
      // and updated since we socketified it
      return results;
    };

    Factory.findOne = function(opts) {
      var result = {};

      Factory.collection.$promise.then(function(){
        result = _.extend(result, $filter('findOne')(Factory.collection, opts));
      });

      socketify(result);

      return result;
    };

    ...

    return Factory;
  };

这是如此之大的原因是你的控制器可以同时简单而又强大。例如,

$scope.users = User.find();

这将返回您可以在视图中使用的所有用户数组;在ng-repeat或其他内容中。它将由套接字更新自动更新/拼接/推送,您无需执行任何额外操作即可。但是等等,还有更多。

$scope.users = User.find({status: "active"});

这将返回所有活动用户的数组。该数组也将由我们的socketify函数自动管理和过滤。因此,如果用户从“活动”更新为“非活动”,则会自动从阵列进行拼接。反之亦然;从“非活动”更新为“活动”的用户将自动添加到阵列中。

其他方法也是如此。

$scope.user = User.findOne({firstname: "Jon"});

如果Jon的电子邮件发生更改,则控制器中的对象会更新。如果他的名字变为“Jonathan”,$scope.user将成为空对象。更好的用户体验将是软删除或只是标记用户以某种方式删除,但这可以在以后添加。

没有$watch$watchCollection$digest$broadcast,必需 - 只是有效。