如何深度观察angularjs中的数组?

How to deep watch an array in angularjs?

在我的作用域中有一个对象数组,我想观察每个对象的所有值。

这是我的代码:

1
2
3
4
5
6
7
8
9
function TodoCtrl($scope) {
  $scope.columns = [
      { field:'title', displayName: 'TITLE'},
      { field: 'content', displayName: 'CONTENT' }
  ];
   $scope.$watch('columns', function(newVal) {
       alert('columns changed');
   });
}

但是,当我修改这些值时,例如我将TITLE更改为TITLE2alert('columns changed')就不会弹出。

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

现场演示:http://jsfiddle.net/syx9b/


您可以将$watch的第3个参数设置为true

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

参见https://docs.angularjs.org/api/ng/type/$rootscope.scope$watch

由于Angular 1.1.x,您还可以使用$watchCollection来观看浅表(只是集合的"第一级")。

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

参见https://docs.angularjs.org/api/ng/type/$rootscope.scope$watchcollection


在你的$watch中深潜一个物体会有性能上的影响。有时(例如,当更改只是推送和弹出时),您可能需要$watch一个容易计算的值,例如array.length。


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

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

例子

但这不适用于多个数组:

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

例子

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

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

例子

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


值得注意的是,在Angular1.1.x及更高版本中,您现在可以使用$watchCollection而不是$watch。虽然$watchCollection似乎创建了浅表,因此它无法像您预期的那样处理对象数组。它可以检测对数组的添加和删除,但不能检测数组中对象的属性。


下面是3种方法的比较,您可以通过示例查看范围变量:

$watch()由以下触发:

1
2
3
$scope.myArray = [];
$scope.myArray = null;
$scope.myArray = someOtherArray;

$watchCollection()由以上所有内容触发,并且:

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

$watch(…,true)由以上所有内容触发,并且:

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

还有一件事…

$watch()是唯一一个在数组替换为另一个数组时触发的数组,即使另一个数组具有相同的精确内容。

例如,如果$watch()会开火,而$watchCollection()不会:

1
2
3
4
5
6
7
8
$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/


$watchCollection完成了您想要做的事情。以下是从AngularJS网站http://docs.angularJS.org/api/ng/type/$rootscope.scope复制的示例虽然它很方便,但需要考虑性能,尤其是当您观看大型收藏时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  $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);


在我的例子中,我需要监视一个包含一个地址对象的服务,该对象也由其他几个控制器监视。我陷入了一个循环,直到我添加了"真"参数,这似乎是观察对象成功的关键。

1
2
3
4
5
$scope.$watch(function() {
    return LocationService.getAddress();
}, function(address) {
    //handle address object
}, true);

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

范围:$watch(attrs.testwatch,function()…..,true);

真的很好地工作,并对所有的技术(添加、删除或修改一个字段)作出反应。

这是一个工作的掠夺者玩它。

深入观察安格拉尔的阵法

我希望这对你有用。如果您有任何问题,请随时咨询,我会尽力帮助您的:)


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

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

皮兰很好地回答了这个问题,并提到了$watchCollection

更多细节

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

问题是消化不会立即发生。它们必须等到当前代码块完成后才能执行。因此,观察数组的length实际上可能会错过$watchCollection将捕获的一些重要更改。

假设此配置:

1
2
3
4
5
6
7
8
9
10
11
12
$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');
});

乍一看,这些可能会同时触发,例如在这种情况下:

1
2
3
4
5
6
7
8
function pushToArray() {
    $scope.testArray.push({val:3});
}
pushToArray();

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

这很有效,但是考虑一下:

1
2
3
4
5
6
7
8
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还是接受了。

1
2
3
4
5
6
7
8
function pushPopArray() {
    $testArray.push({val: 3});
    $testArray.pop();
}
pushPopArray();

// Console output
// testArray change

相同的结果发生在同一个块中的推和弹出。

结论

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

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

不要对数组的length属性使用$watch。我想这样做几乎没有什么好理由。


1
2
3
4
5
6
7
8
$scope.changePass = function(data){
   
    if(data.txtNewConfirmPassword !== data.txtNewPassword){
        $scope.confirmStatus = true;
    }else{
        $scope.confirmStatus = false;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  <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>      
       
      <span style="color:red" ng-show="myForm.checknawPassword.$error.minlength || myForm.checkConfirmPassword.$error.minlength">??????????????????? 5 ????</span>
      <span ng-show="confirmStatus" style="color:red">?????????????????????</span>
     
      <button class="button button-positive  button-block" ng-click="saveChangePass(data)" ng-disabled="myForm.$invalid || confirmStatus">???????</button>
    </form>