如何在angularjs中深入观察数组?


320

我的范围内有一个对象数组,我想观察每个对象的所有值。

这是我的代码:

function TodoCtrl($scope) {
  $scope.columns = [
      { field:'title', displayName: 'TITLE'},
      { field: 'content', displayName: 'CONTENT' }
  ];
   $scope.$watch('columns', function(newVal) {
       alert('columns changed');
   });
}

但是,当我修改值时,例如,我更改TITLETITLE2,则alert('columns changed')永不弹出。

如何深入观察数组中的对象?

有一个现场演示:http : //jsfiddle.net/SYx9b/

Answers:


529

您可以将的第3个参数设置$watchtrue

$scope.$watch('data', function (newVal, oldVal) { /*...*/ }, true);

参见https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch

从Angular 1.1.x开始,您还可以使用$ watchCollection监视集合的浅表(仅是其“第一级”)。

$scope.$watchCollection('data', function (newVal, oldVal) { /*...*/ });

参见https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watchCollection


2
angular.equals当第三个参数采用布尔值时,为什么要使用?
JayQuerie.com

感谢Trevor,对文档的误读。我已经更新了上面的代码,并更新了我的代码以使其匹配。
皮兰

36
在1.1.x中,您现在拥有$watchCollection
Jonathan Rowny,2013年

39
$watchCollection据我了解,它将只会观看数组或对象的“第一级”。如果您需要更深入地了解,则以上答案是正确的。bennadel.com/blog/...
Blazemonger

1
好答案...如果我可以的话,将给您超过一票。:)谢谢
Jony-Y

50

对$ watch中的对象进行深度潜水会对性能产生影响。有时(例如,当更改仅是推送和弹出时),您可能希望监视一个容易计算的值,例如array.length。


1
这应该有更多的选票。深度观看很昂贵。我知道OP一直在寻找深入的观察,但是人们可能只是想知道数组本身是否发生变化而来到这里。在这种情况下,观看长度要快得多。
斯科特·席尔维

42
这应该是评论,而不是答案。
Blazemonger 2014年

1
如@Blazemonger评论(stackoverflow.com/questions/14712089#comment32440226_14713978)所述,可以通过使用$ watchCollection来最大程度地降低性能问题。
肖恩·比恩

整洁的建议。关于此评论不是答案,我认为最好是提出满足答案接受标准的建议。我认为,采用这种方法可以很好地解决这个问题。
艾米·佩莱格里尼

43

如果只看一个数组,则只需使用以下代码即可:

$scope.$watch('columns', function() {
  // some value in the array has changed 
}, true); // watching properties

但这不适用于多个数组:

$scope.$watch('columns + ANOTHER_ARRAY', function() {
  // will never be called when things change in columns or ANOTHER_ARRAY
}, true);

为了处理这种情况,我通常将要观看的多个数组转换为JSON:

$scope.$watch(function() { 
  return angular.toJson([$scope.columns, $scope.ANOTHER_ARRAY, ... ]); 
},
function() {
  // some value in some array has changed
}

正如@jssebastian在评论中指出的那样,JSON.stringify可能更可取,angular.toJson因为它可以处理以'$'开头的成员以及其他可能的情况。


我还发现有第3个参数$watch,它能够做到相同吗?Pass true as a third argument to watch an object's properties too.请参阅:cheatography.com/proloser/cheat-sheets/angularjs
Freewind

@Freewind如果曾经需要观看多个数组,它将失败,如此处所示。但是,是的,它也将起作用并且提供与angular.toJson在单个阵列上使用相同的功能。
JayQuerie.com

2
请注意,angular.toJson似乎不包括以'$'开头的include成员:angular.toJson({“ $ hello”:“ world”})只是“ {}”。我使用JSON.stringify()作为替代
jssebastian

@ jssebastian谢谢-我已经更新了答案以包含该信息。
JayQuerie.com 2013年

1
我们可以知道哪个属性发生了变化吗?
2014年

21

值得注意的是,在Angular 1.1.x及更高版本中,您现在可以使用$ watchCollection而不是$ watch。尽管$ watchCollection似乎可以创建浅表,所以它不能与您期望的对象数组一起使用。它可以检测到数组的添加和删除,但不能检测数组内对象的属性。


5
但是,$ watchCollection仅监视浅表。因此,据我了解,更改数组项的属性(如问题中所示)不会触发该事件。
Tarnschaf 2013年

3
这应该是评论,而不是答案。
Blazemonger 2014年

18

这是您可以通过示例观察范围变量的3种方式的比较:

$ watch()由以下条件触发:

$scope.myArray = [];
$scope.myArray = null;
$scope.myArray = someOtherArray;

$ watchCollection()由AND以上的所有内容触发:

$scope.myArray.push({}); // add element
$scope.myArray.splice(0, 1); // remove element
$scope.myArray[0] = {}; // assign index to different value

$ watch(...,true)由AND上方的所有内容触发:

$scope.myArray[0].someProperty = "someValue";

还有一件事...

$ watch()是当一个数组被另一个数组替换时触发的唯一命令,即使该另一个数组具有相同的确切内容。

例如,在哪里$watch()会开火而$watchCollection()不会:

$scope.myArray = ["Apples", "Bananas", "Orange" ];

var newArray = [];
newArray.push("Apples");
newArray.push("Bananas");
newArray.push("Orange");

$scope.myArray = newArray;

下面是一个示例JSFiddle的链接,该示例使用所有不同的监视组合并输出日志消息以指示触发了哪些“监视”:

http://jsfiddle.net/luisperezphd/2zj9k872/


确切地说,特别是要注意“一件大事”
Andy Ma

12

$ watchCollection完成您想要做的事情。以下是从angularjs网站http://docs.angularjs.org/api/ng/type/$rootScope.Scope复制的示例。为了 方便起见,需要考虑性能,尤其是在观看大量收藏时。

  $scope.names = ['igor', 'matias', 'misko', 'james'];
  $scope.dataCount = 4;

  $scope.$watchCollection('names', function(newNames, oldNames) {
     $scope.dataCount = newNames.length;
  });

  expect($scope.dataCount).toEqual(4);
  $scope.$digest();

  //still at 4 ... no changes
  expect($scope.dataCount).toEqual(4);

  $scope.names.pop();
  $scope.$digest();

  //now there's been a change
  expect($scope.dataCount).toEqual(3);

14
OP指定了一个对象数组。您的示例使用字符串数组,但$ watchCollection不适用于对象数组。
KevinL 2014年

4

这个解决方案对我来说非常有效,我正在指令中这样做:

scope。$ watch(attrs.testWatch,function(){.....},true);

true效果很好,并且可以对所有变更(添加,删除或修改字段)做出反应。

这是一个工作的弹拨器。

深入观察AngularJS中的数组

希望对您有用。如果您有任何疑问,请随时提问,我将尽力帮助:)


4

就我而言,我需要监视一个服务,其中包含一个地址对象,该地址对象也被其他几个控制器监视。在添加“ true”参数之前,我一直处于循环状态,这似乎是观察对象成功的关键。

$scope.$watch(function() {
    return LocationService.getAddress();
}, function(address) {
    //handle address object
}, true);

1

设置objectEquality函数的参数(第三个参数)$watch绝对是观察数组所有属性的正确方法。

$scope.$watch('columns', function(newVal) {
    alert('columns changed');
},true); // <- Right here

皮兰(Piran)回答得足够好,也提到$watchCollection了。

更多详情

我之所以回答已经回答的问题,是因为我想指出Wizardwerdna的答案不是一个好答案,不应该使用。

问题在于摘要不会立即发生。他们必须等到当前代码块完成后才能执行。因此,观看length数组实际上可能会错过一些重要的变化,这些变化$watchCollection将引起关注。

假设此配置:

$scope.testArray = [
    {val:1},
    {val:2}
];

$scope.$watch('testArray.length', function(newLength, oldLength) {
    console.log('length changed: ', oldLength, ' -> ', newLength);
});

$scope.$watchCollection('testArray', function(newArray) {
    console.log('testArray changed');
});

乍一看,它们似乎会同时触发,例如在这种情况下:

function pushToArray() {
    $scope.testArray.push({val:3});
}
pushToArray();

// Console output
// length changed: 2 -> 3
// testArray changed

那已经足够好了,但是考虑一下:

function spliceArray() {
    // Starting at index 1, remove 1 item, then push {val: 3}.
    $testArray.splice(1, 1, {val: 3});
}
spliceArray();

// Console output
// testArray changed

请注意,所产生的长度是一样的,尽管阵中拥有新的元素,失去的元素,以便作为手表$watch而言,length并没有改变。 $watchCollection捡起它。

function pushPopArray() {
    $testArray.push({val: 3});
    $testArray.pop();
}
pushPopArray();

// Console output
// testArray change

在同一块中按下并弹出会产生相同的结果。

结论

要监视数组中的每个属性,请在数组$watch本身上使用一个包含第三个参数(objectEquality)并将其设置为true的属性。是的,这很昂贵,但有时是必需的。

要观察对象何时进入/退出数组,请使用$watchCollection

不要在数组$watchlength属性上使用a 。我几乎没有理由想到这样做。


0

$scope.changePass = function(data){
    
    if(data.txtNewConfirmPassword !== data.txtNewPassword){
        $scope.confirmStatus = true;
    }else{
        $scope.confirmStatus = false;
    }
};
  <form class="list" name="myForm">
      <label class="item item-input">        
        <input type="password" placeholder="ใส่รหัสผ่านปัจจุบัน" ng-model="data.txtCurrentPassword" maxlength="5" required>
      </label>
      <label class="item item-input">
        <input type="password" placeholder="ใส่รหัสผ่านใหม่" ng-model="data.txtNewPassword" maxlength="5" ng-minlength="5" name="checknawPassword" ng-change="changePass(data)" required>
      </label>
      <label class="item item-input">
        <input type="password" placeholder="ใส่รหัสผ่านใหม่ให้ตรงกัน" ng-model="data.txtNewConfirmPassword" maxlength="5" ng-minlength="5" name="checkConfirmPassword" ng-change="changePass(data)" required>
      </label>      
       <div class="spacer" style="width: 300px; height: 5px;"></div> 
      <span style="color:red" ng-show="myForm.checknawPassword.$error.minlength || myForm.checkConfirmPassword.$error.minlength">รหัสผ่านต้องมีจำนวน 5 หลัก</span><br>
      <span ng-show="confirmStatus" style="color:red">รหัสผ่านใหม่ไม่ตรงกัน</span>
      <br>
      <button class="button button-positive  button-block" ng-click="saveChangePass(data)" ng-disabled="myForm.$invalid || confirmStatus">เปลี่ยน</button>
    </form>

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.