Làm thế nào để xem sâu một mảng trong angularjs?


320

Có một mảng các đối tượng trong phạm vi của tôi, tôi muốn xem tất cả các giá trị của từng đối tượng.

Đây là mã của tôi:

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

Nhưng khi tôi sửa đổi các giá trị, ví dụ tôi thay đổi TITLEthành TITLE2, alert('columns changed')không bao giờ xuất hiện.

Làm thế nào để theo dõi sâu các đối tượng bên trong một mảng?

Có bản demo trực tiếp: http://jsfiddle.net/SYx9b/

Câu trả lời:


529

Bạn có thể thiết lập các tham số thứ 3 của $watchđể true:

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

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

Kể từ Angular 1.1.x, bạn cũng có thể sử dụng $ watchCollection để xem đồng hồ nông (chỉ là "cấp độ đầu tiên") của bộ sưu tập.

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

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


2
Tại sao bạn sử dụng angular.equalskhi đối số thứ ba có giá trị boolean ?
JayQuerie.com

Cảm ơn Trevor, đọc sai các tài liệu. Tôi đã cập nhật mã ở trên và cập nhật mã của mình để khớp.
Piran

36
trong 1.1.x bây giờ bạn có$watchCollection
Jonathan Rowny

39
$watchCollectionsẽ chỉ xem "cấp độ đầu tiên" của một mảng hoặc đối tượng, như tôi hiểu. Câu trả lời trên là chính xác nếu bạn cần xem sâu hơn thế. bennadel.com/blog/ từ
Blazemonger

1
Câu trả lời tuyệt vời ... sẽ cho bạn nhiều hơn một phiếu bầu nếu tôi có thể ... :) cảm ơn
Jony-Y

50

Có những hậu quả về hiệu suất khi lặn sâu một đối tượng trong chiếc đồng hồ $ của bạn. Đôi khi (ví dụ: khi các thay đổi chỉ được đẩy và bật), bạn có thể muốn $ xem một giá trị được tính toán dễ dàng, chẳng hạn như mảng.length.


1
Điều này nên có nhiều phiếu hơn. Xem sâu là đắt. Tôi hiểu OP đang tìm kiếm sự theo dõi sâu sắc, nhưng mọi người có thể đến đây chỉ muốn biết liệu chính mảng đó có thay đổi hay không. Xem chiều dài nhanh hơn nhiều, trong trường hợp đó.
Scott Silvi

42
Đây phải là một bình luận, không phải là một câu trả lời.
Blazemonger

1
Các vấn đề về hiệu suất sẽ được giảm thiểu bằng cách sử dụng $ watchCollection, như được đề cập bởi bình luận @Blazemonger ( stackoverflow.com/questions/14712089#comment32440226_14713978 ).
Sean the Bean

Lời khuyên gọn gàng. Về nhận xét này không phải là một câu trả lời, theo tôi, tốt hơn là nên xây dựng đề xuất để hoàn thành các tiêu chí chấp nhận cho một câu trả lời. Tôi nghĩ rằng với cách tiếp cận này, một giải pháp tao nhã cho câu hỏi có thể đạt được.
Amy Pellegrini

43

Nếu bạn chỉ xem một mảng, bạn chỉ cần sử dụng đoạn mã này:

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

thí dụ

Nhưng điều này sẽ không hoạt động với nhiều mảng:

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

thí dụ

Để xử lý tình huống này, tôi thường chuyển đổi nhiều mảng tôi muốn xem thành JSON:

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

thí dụ

Như @jssebastian đã chỉ ra trong các bình luận, JSON.stringifycó thể thích hợp hơn angular.toJsonvì nó có thể xử lý các thành viên bắt đầu bằng '$' và các trường hợp khác cũng có thể xảy ra.


Tôi cũng thấy có một tham số thứ 3 $watch, nó có thể làm như vậy không? Pass true as a third argument to watch an object's properties too.Xem: cheatography.com/proloser/cheat-sheet/angularjs
Freewind

@Freewind Nếu có trường hợp bạn cần xem nhiều mảng thì sẽ thất bại như đã thấy ở đây . Nhưng có, điều đó cũng sẽ hoạt động và cung cấp chức năng tương tự như sử dụng angular.toJsontrên một mảng duy nhất.
JayQuerie.com

2
Chỉ cần lưu ý rằng angular.toJson dường như không bao gồm các thành viên bắt đầu bằng '$': angular.toJson ({"$ hello": "world"}) chỉ là "{}". Tôi đã sử dụng JSON.opesify () thay thế
jssebastian

@ jssebastian Cảm ơn - Tôi đã cập nhật câu trả lời để bao gồm thông tin đó.
JayQuerie.com

1
chúng ta có thể biết tài sản nào đã thay đổi?
Ashwin

21

Điều đáng chú ý là trong Angular 1.1.x trở lên, giờ đây bạn có thể sử dụng $ watchCollection thay vì $ watch. Mặc dù $ watchCollection xuất hiện để tạo ra những chiếc đồng hồ nông nên nó sẽ không hoạt động với các đối tượng như bạn mong đợi. Nó có thể phát hiện các bổ sung và xóa vào mảng, nhưng không phát hiện các thuộc tính của các đối tượng bên trong mảng.


5
$ watchCollection chỉ xem đồng hồ nông, tuy nhiên. Vì vậy, theo tôi hiểu, việc thay đổi các thuộc tính của các mục của mảng (như trong câu hỏi) sẽ không kích hoạt sự kiện.
Tarnschaf

3
Đây phải là một bình luận, không phải là một câu trả lời.
Blazemonger

18

Dưới đây là so sánh về 3 cách bạn có thể xem biến phạm vi với các ví dụ:

$ watch () được kích hoạt bởi:

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

$ watchCollection () được kích hoạt bởi mọi thứ ở trên VÀ:

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

$ watch (..., true) được kích hoạt bởi MỌI THỨ ở trên VÀ:

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

CHỈ CÓ MỘT THÊM ...

$ watch () là cái duy nhất kích hoạt khi một mảng được thay thế bằng một mảng khác ngay cả khi mảng khác đó có cùng nội dung chính xác.

Ví dụ, nơi $watch()sẽ bắn và $watchCollection()sẽ không:

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

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

$scope.myArray = newArray;

Dưới đây là một liên kết đến một ví dụ JSFiddle sử dụng tất cả các kết hợp đồng hồ và thông báo nhật ký đầu ra khác nhau để cho biết "đồng hồ" nào đã được kích hoạt:

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


Chính xác, đặc biệt lưu ý 'MỘT THÊM NHIỀU'
Andy Ma

12

$ watchCollection hoàn thành những gì bạn muốn làm. Dưới đây là một ví dụ được sao chép từ trang web angularjs http://docs.angularjs.org/api/ng/type/$rootScope.Scope Trong khi thuận tiện, hiệu suất cần được xem xét đặc biệt là khi bạn xem một bộ sưu tập lớn.

  $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 chỉ định một mảng các đối tượng. Ví dụ của bạn hoạt động với một chuỗi các chuỗi, nhưng $ watchCollection không hoạt động với một mảng các đối tượng.
KevinL

4

Giải pháp này hoạt động rất tốt đối với tôi, tôi đang thực hiện điều này trong một chỉ thị:

phạm vi. $ watch (attrs.testWatch, function () {.....}, true);

sự thật hoạt động khá tốt và phản ứng cho tất cả các chnages (thêm, xóa hoặc sửa đổi một trường).

Đây là một plunker làm việc để chơi với nó.

Xem sâu một mảng trong AngularJS

Tôi hy vọng điều này có thể hữu ích cho bạn. Nếu bạn có bất kỳ câu hỏi nào, cứ thoải mái hỏi, tôi sẽ cố gắng giúp :)


4

Trong trường hợp của tôi, tôi cần xem một dịch vụ chứa đối tượng địa chỉ cũng được xem bởi một số bộ điều khiển khác. Tôi bị mắc kẹt trong một vòng lặp cho đến khi tôi thêm tham số 'true', dường như là chìa khóa thành công khi xem các đối tượng.

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

1

Đặt objectEqualitytham số (tham số thứ ba) của $watchhàm chắc chắn là cách chính xác để xem TẤT CẢ các thuộc tính của mảng.

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

Piran trả lời điều này đủ tốt và cũng đề cập đến $watchCollection.

Thêm chi tiết

Lý do tôi trả lời một câu hỏi đã được trả lời là vì tôi muốn chỉ ra rằng câu trả lời của wizardwerdna không phải là câu hỏi hay và không nên sử dụng.

Vấn đề là các tiêu hóa không xảy ra ngay lập tức. Họ phải đợi cho đến khi khối mã hiện tại hoàn thành trước khi thực thi. Vì vậy, xem lengthmột mảng thực sự có thể bỏ lỡ một số thay đổi quan trọng $watchCollectionsẽ bắt.

Giả sử cấu hình này:

$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');
});

Thoạt nhìn, có vẻ như những thứ này sẽ bắn cùng một lúc, chẳng hạn như trong trường hợp này:

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

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

Điều đó hoạt động đủ tốt, nhưng xem xét điều này:

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

// Console output
// testArray changed

Lưu ý rằng độ dài kết quả là như nhau mặc dù mảng có một phần tử mới và bị mất một phần tử, do đó, khi xem $watchcó liên quan, lengthđã không thay đổi. $watchCollectionnhặt lên trên, mặc dù.

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

// Console output
// testArray change

Kết quả tương tự xảy ra với một lần đẩy và bật trong cùng một khối.

Phần kết luận

Để xem mọi thuộc tính trong mảng, hãy sử dụng a $watchtrên chính mảng đó với tham số thứ ba (objectEquality) được bao gồm và đặt thành true. Vâng, điều này là đắt tiền nhưng đôi khi cần thiết.

Để xem khi đối tượng nhập / thoát mảng, sử dụng a $watchCollection.

KHÔNG sử dụng a $watchtrên thuộc lengthtính của mảng. Hầu như không có lý do chính đáng nào tôi có thể nghĩ ra để làm như vậy.


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>

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.