用ngResource、socket维护一个资源集合.IO和$q
Maintaining a resource collection with ngResource, socket.io and $q
我正在尝试创建一个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;
});
}
});
然而,没有人喜欢变通方法,特别是当它涉及到控制器中的冗余代码时。我在这个问题上追加赏金,寻找能在工厂内部解决这个问题的人。
- 不要在
Factory
上公开collection
属性,将其保留为局部变量。 - 在Factory上创建一个新的暴露的
getter/setter
,用于代理本地变量。 - 在
find
方法中使用getter/setter对象。
像这样:
// internal variable
var collection = Resource.query();
// exposed 'proxy' object
Object.defineProperty(Factory, 'collection', {
get: function () {
return collection;
},
set: function (item) {
// If we got a finite Integer.
if (_.isFinite(item)) {
collection.splice(item, 1);
}
// Check if the given item is already in the collection.
var idx = _.findIndex(Factory.collection, function(u) {
return u._id === item._id;
});
if (idx) {
// Update the item in the collection.
collection[idx] = item;
} else {
// Push the new item to the collection.
collection.push(item);
}
// Trigger the $digest cycle as a last step after modifying the collection.
// Can safely be moved to Socket listeners so as to not trigger unnecessary $digests from an angular function.
$rootScope.$digest();
}
});
/**
* Change all calls from 'Factory.collection.push(item)' to
* 'Factory.collection = item;'
*
* Change all calls from 'Factory.collection[idx] = item' to
* 'Factory.collection = item;'
*
* Change all calls from 'Factory.collection.splice(idx, 1) to
* 'Factory.collection = idx;'
*
*/
现在,看看非角方如何修改集合(在本例中即socket),您将需要触发$digest
循环以反映集合的新状态。如果您只对在单个$scope
(或多个,但不是跨作用域)中保持集合同步感兴趣,我会将$scope
附加到工厂,并在那里运行$digest
而不是$rootScope
。这将为您节省一点性能。
这里有一个jsbin展示了Object.getter
的使用将如何保持你的集合同步,并允许你找到最近添加到集合中的项目。
我在jsbin中选择了setTimeout
,这样就不会通过使用$interval
来触发自动的$digests
。
显然jsbin是非常简陋的;没有承诺被打乱,没有套接字连接。我只是想展示一下如何保持同步。
我承认Factory.collection = value
看起来很糟糕,但是你可以在包装函数的帮助下隐藏它,使它更漂亮/更好读。
解决方案是让工厂方法返回一个空的对象/数组以便稍后填充(类似于ngResource的工作方式)。然后将套接字侦听器连接到这些返回对象/数组和主工厂。数组集合。
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();
返回一个包含所有用户的数组,可以在视图中使用;在一个n -repeat或别的什么。它将自动被来自套接字的更新更新/拼接/推送,你不需要做任何额外的事情来获得它。但是等等,还有更多。
$scope.users = User.find({status: "active"});
返回一个包含所有活动用户的数组。该数组也将由socketify函数自动管理和过滤。因此,如果一个用户从"活跃"更新为"不活跃",他将自动从数组中拼接出来。反之亦然;从"非活动"更新为"活动"的用户将自动添加到数组中。
其他方法也是如此。
$scope.user = User.findOne({firstname: "Jon"});
如果Jon的电子邮件更改,则控制器中的对象更新。如果他的名字变成了"Jonathan",$scope.user
就变成了一个空对象。更好的用户体验应该是软删除,或者只是以某种方式标记用户已删除,但这可以稍后添加。
不需要$watch
, $watchCollection
, $digest
, $broadcast
-它只是工作
- 如何使jQuery插件函数可调用以供独立使用,而不在集合上操作
- Meteor-将选定窗体中的对象添加到集合中
- 主干集合重置和解析
- 使用数据上的角度更改设置集合的第一个元素的动画
- 为集合分配大量的模型弹药
- 如何使用backbone.js从集合中获取模型名称
- Lodash从集合创建集合
- 否'访问控制允许来源'标头存在于IISNOde中请求的资源(AngularJS+NodeJs)上
- WebResources嵌入式资源和Javascript如何在另一个嵌入式资源中使用它们
- 2个backbone.js集合,具有相同的模型,但排序顺序不同
- 如何在构建node-webkit应用程序后获取外部资源
- ADF:有条件地加载javascript资源
- Javascript嵌入式图像资源
- 内容安全策略:页面's设置阻止加载资源
- 如何合并不同集合的游标并按日期排序
- 用于查询错误转换的角度资源返回列表
- 将当前用户的 ID 推送到 meteorjs 中集合/文档的内部数组
- 未能在Laravel中加载资源
- 如何使用Angular 2服务提供包含指向另一个资源的链接/ids的资源集合
- 用ngResource、socket维护一个资源集合.IO和$q