我有一个包含10个物体的地图。每个对象都有10个属性val1,...,val10。现在要检查地图中特定对象的特定属性是否已更改,我需要编写$ scope。$ watch 100次。如何观看整个地图/ JSobject并确切知道哪个属性发生了变化?
/*watch individual property*/
$scope.$watch('map[someid].val1', function(new_val, old_val)
{
//do something
});
/*watch entire object, how would this help me?*/
$scope.$watch('map', function(new_val, old_val)
{
//do something
}, true);
答案 0 :(得分:5)
由于之前的答案似乎表明AngularJS没有为您的问题提供有针对性的解决方案,让我建议使用vanilla JS。
AFAIK如果你想实现对象的观察者,你需要某种getter / setter模式(aka。Mutator method)。我对AngularJS一无所知,但我确信他们必须使用类似的东西。有多种方法可以在JavaScript中实现getter和setter(参见上面链接的Wikipedia页面),但一般来说你有三个主要选择:
出于某种原因,我已经看到了更多通过前两种方法做到这一点的例子,但另一方面,我看到了一个用第三种方法构建的相当复杂和漂亮的API的例子。我将给你一个使用Object.defineProperty通过第三种方法解决问题的例子:
我们的想法是提供一个观察者功能,然后将其作为属性设置器的一部分进行调用:
function watcher(objectIndex, attributeName) {
console.log('map[' + objectIndex + '].' + attributeName + ' has changed');
}
Object.defineProperty有点像为对象定义属性的复杂方式。所以当你通常写的时候
obj.val1 = "foo";
你要写
Object.defineProperty(obj, 'val1', {
get: function () { /* ... */ },
set: function (newValue) { /* ... */ },
// possible other parameters for defineProperty
})
IE中。您可以定义在读取和写入obj.val1时要执行的操作。这使您可以将您的观察者功能称为setter的一部分:
Object.defineProperty(obj, 'val1', {
get: function () { /* ... */ },
set: function (newValue) {
// set the value somewhere; just don't use this.val1
/* call the */ watcher(/* with args, of course */)
}
})
对于非常简单的事情来说,似乎有很多代码行,但是您可以使用函数式编程技术和闭包来构建程序,这样它实际上并不是那么复杂。所以从本质上讲,尽管这可能看起来不像是一个" AngularJS-way"在做事情上,手工实施的开销实际上并不多。
以下是针对您的问题的完整代码示例:http://codepen.io/cido/pen/DEpLj/
在示例中,观察器功能很难编码为数据结构实现的一部分。但是,如果您需要在多个位置重复使用此代码,则没有什么能阻止您使用例如扩展解决方案。 addEventListener-stylish hooks等。
由于这种方法非常使用闭包,如果你真的处理大量数据,它可能会有一些内存含义。但是,在大多数用例中,您不必担心这类事情。而另一方面,所有解决方案都基于"深度平等"支票也不会是超级内存或快速的。
答案 1 :(得分:4)
目前AngularJS中没有任何内容可以帮助您处理此问题。
您必须自己实施或找到适合您的库。
如果您想使用任何内置观察器功能自行实现它,有三个选项:
<强> 1。 $ watch with false as third parameter:比较以供参考 平等。在你的情况下不是一个选项。
<强> 2。 $ watchCollection:将浅看地图中的十个对象。不是一种选择。
第3。 $ watch使用true作为第三个参数:将使用angular.equals
比较对象的相等性。适用于您的用例。
下面是一个示例,它将整个地图的观察者注册为true作为第三个参数,并手动进行比较。
注意:根据您的应用程序(例如绑定次数和对$ digest的调用),观看大型复杂对象可能会对内存和性能产生不利影响。由于此示例使用另一个angular.copy
($ watch至少在内部使用一个),因此会对性能产生更大影响。
$scope.map = {};
for (var i = 0; i < 3; i++) {
$scope.map[i] = {
property1: 'value1',
property2: 'value2',
property3: 'value3'
};
}
var source = [];
var updateSource = function(newSource) {
angular.copy(newSource, source);
};
updateSource($scope.map);
$scope.$watch("map", function(newMap, previousMap) {
if (newMap !== previousMap) {
for (var id in newMap) {
if (angular.equals(newMap[id], source[id])) continue;
var newObject = newMap[id];
var previousObject = source[id];
for (var property in newObject) {
if (!newObject.hasOwnProperty(property) ||
angular.equals(newObject[property], previousObject[property])) continue;
var cd = $scope.changeDetection = {};
cd.changedObjectId = id;
cd.previousObject = previousObject;
cd.newObject = newObject;
cd.changedProperty = property;
cd.previousPropertyValue = previousObject[property];
cd.newPropertyValue = newObject[property];
}
updateSource(newMap);
}
}
}, true);
答案 2 :(得分:2)
我会尝试使用角度文档
$scope.$watchCollection(collection, function(newValue, oldValue) {
//do stuff when collection changes
});
http://docs.angularjs.org/api/ng/type/ $ rootScope.Scope
答案 3 :(得分:2)
$ watch有一个不太知名的第三个参数,如果为true,将使用angular.equals()而不是使用===来测试相等性。现在angular.equals()确实考虑两个对象等于一组条件中的任何一个是真的,其中一个是:
- 通过将它们与angular.equals进行比较,两个对象或值都属于同一类型,并且它们的所有属性都相同。
这基本上是说它会进行递归(深度)比较,这就是你需要的。因此,检测任何嵌套属性的更改都非常简单:
scope.$watch('map', function (newmap,oldmap) {
// detect what changed manually with your own recursive function
}, true);
这不会告诉你究竟发生了什么变化,所以你需要自己做这个部分。考虑直接将您的视图绑定到map
,并确保所有更改都发生在角度$ digest周期内,或者将它们包含在$ apply中,以便更轻松地解决问题。如果您的用例必须使用$ watch,您还可以递归迭代对象并在那里设置单独的手表。
答案 4 :(得分:1)
我知道我迟到了,但我需要类似的东西,以上的答案都没有帮助。
我使用Angular的$ watch函数来检测变量的变化。我不仅需要知道属性是否在变量上发生了变化,而且我还想确保更改的属性不是临时的计算字段。换句话说,我想忽略某些属性。
以下是代码:https://jsfiddle.net/rv01x6jo/
以下是如何使用它:
$scope.$watch($scope.MyObjectToWatch, function(newValue, oldValue) {
// To only return the difference
var difference = diff(newValue, oldValue);
// To exclude certain properties from being evaluated (i.e. temporary/calculated/meta properties)
var difference = diff(newValue, oldValue, [newValue.prop1, newValue.prop2, newValue.prop3]);
if (Object.keys(difference).length == 0)
return; // Nothing has changed
else
doSomething(); // Something has changed
});
通过检查Object.keys(difference)
数组,您可以看到所有已更改的属性。
希望这有助于某人。
答案 5 :(得分:0)
一种方法是编写一个小函数来展平对象now
和then
并形成字符串。从那时起,比较字符串并找到不匹配点非常简单,这是对象发生变化的点。
毋庸置疑,所有对象属性都应该公开。
var flatten = function (key, obj) {
var ret = '';
$.each(obj, function (_key, val) {
if (typeof val == 'object') {
ret += flatten(key + '.' + _key, val);
} else {
ret += ((key ? key + '.' : '') + _key + ':' + val + ',');
}
});
return ret;
};
其次是:
$scope.$watch(obj, function (now, then) {
var nowFlat = flatten(now),
thenFlat = flatten(then);
compareStrings(nowFlat, thenFlat); // add custom string compare function here
}, true)
作为性能改进,您可以随时缓存nowFlat
,并在下次触发thenFlat
时将其用作$watch
。
答案 6 :(得分:0)
我知道我迟到了,但是我需要类似上面的答案没有用的东西。
我使用Angular的$ watch函数来检测变量的变化。我不仅需要知道属性是否在变量上发生了变化,而且我还想确保更改的属性不是临时的计算字段。换句话说,我想忽略某些属性。
以下是代码:https://jsfiddle.net/rv01x6jo/
以下是如何使用它:
$scope.$watch($scope.MyObjectToWatch, function(newValue, oldValue) {
// To only return the difference
var difference = diff(newValue, oldValue);
// To exclude certain properties from being evaluated (i.e. temporary/calculated/meta properties)
var difference = diff(newValue, oldValue, [newValue.prop1, newValue.prop2, newValue.prop3]);
if (Object.keys(difference).length == 0)
return; // Nothing has changed
else
doSomething(); // Something has changed
});
希望这有助于某人。