Как глубоко посмотреть массив в angularjs?

290

В моей области есть массив объектов, я хочу посмотреть все значения каждого объекта.

Это мой код:

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

Но когда я изменяю значения, например. Я изменяю TITLE на TITLE2, alert('columns changed') никогда не выскочил.

Как глубоко смотреть объекты внутри массива?

Существует живая демонстрация: http://jsfiddle.net/SYx9b/

Теги:
watch

10 ответов

501
Лучший ответ

Вы можете установить третий аргумент $watch на true:

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

См. http://docs.angularjs.org/api/ng.$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 когда третий аргумент принимает логическое значение ?
  • 0
    Спасибо Тревор, неверное прочтение документов. Я обновил код выше и обновил мой код, чтобы соответствовать.
Показать ещё 12 комментариев
47

Есть последствия производительности для глубокого погружения объекта в ваши $watch. Иногда (например, когда изменения только толкаются и появляются), вы можете захотеть $смотреть легко рассчитанное значение, например array.length.

  • 1
    Это должно иметь больше голосов. Глубокий просмотр дорог. Я понимаю, что OP искал глубокое наблюдение, но люди могут прийти сюда, просто желая узнать, изменился ли сам массив. В этом случае смотреть длину намного быстрее.
  • 37
    Это должен быть комментарий, а не ответ.
Показать ещё 2 комментария
41

Если вы собираетесь смотреть только один массив, вы можете просто использовать этот бит кода:

$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, поскольку он может обрабатывать членов, которые начинаются с '$' и других возможных случаев.

  • 0
    Я также обнаружил, что есть 3-й параметр для $watch , может ли он сделать то же самое? Pass true as a third argument to watch an object's properties too. Смотрите: cheatography.com/proloser/cheat-sheets/angularjs
  • 0
    @Freewind Если есть случай, когда вам нужно будет смотреть несколько массивов, он потерпит неудачу, как показано здесь . Но да, это тоже будет работать и обеспечивает такую же функциональность, как и использование angular.toJson для одного массива.
Показать ещё 7 комментариев
21

Стоит отметить, что в Angular 1.1.x и выше вы можете теперь использовать $watchCollection, а не $watch. Хотя $watchCollection, похоже, создает мелкие часы, поэтому он не будет работать с массивами объектов, как вы ожидаете. Он может обнаруживать добавления и удаления массива, но не свойства объектов внутри массивов.

  • 5
    Тем не менее, $ watchCollection наблюдает только поверхностно. Поэтому, насколько я понимаю, изменение свойств элементов массива (как в вопросе) не вызовет событие.
  • 0
    Да, спасибо за разъяснения. Я отредактирую свой ответ.
Показать ещё 1 комментарий
13

$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 been a change
  expect($scope.dataCount).toEqual(3);
  • 13
    ОП указал массив объектов. Ваш пример работает с массивом строк, но $ watchCollection не работает с массивом объектов.
  • 0
    Почему бы вам не прочитать полный вопрос!
8

Ниже приведено сравнение трех способов просмотра переменных области с примерами:

$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/

  • 0
    Это лучший ответ, спасибо.
  • 0
    Точно, особенно отметить «ОДНА БОЛЬШЕ»
4

В моем случае мне нужно было посмотреть сервис, который содержит адресный объект, также наблюдаемый несколькими другими контроллерами. Я застрял в цикле, пока не добавил параметр "истина", который, кажется, является ключом к успеху при просмотре объектов.

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

Это решение работало очень хорошо для меня, я делаю это в директиве:

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

true работает очень хорошо и реагирует на все chnages (добавляет, удаляет или модифицирует поле).

Вот рабочий плункер для игры с ним.

Глубоко просматривая массив в AngularJS

Я надеюсь, что это может быть полезно для вас. Если у вас есть какие-либо вопросы, не стесняйтесь спрашивать, я постараюсь помочь:)

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.

НЕ используйте $watch в свойстве length для массива. Я почти не думаю, что это можно сделать.

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>

Ещё вопросы

Сообщество Overcoder
Наверх
Меню