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


320

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

Это мой код:

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/

Ответы:


529

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

$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

Спасибо Тревор, неверное прочтение документов. Я обновил код выше и обновил свой код, чтобы он соответствовал.
Пиран

36
в 1.1.x теперь у вас есть$watchCollection
Джонатан Ровни

39
$watchCollectionбудет смотреть только «первый уровень» массива или объекта, насколько я понимаю. Приведенный выше ответ является правильным, если вам нужно смотреть глубже, чем это. bennadel.com/blog/…
Blazemonger

1
Отличный ответ ... дал бы вам больше одного голоса, если бы я мог ... :) спасибо
Jony-Y

50

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


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

42
Это должен быть комментарий, а не ответ.
Blazemonger

1
Проблемы с производительностью будут минимизированы с помощью $ watchCollection, как отмечено в комментарии @Blazemonger ( stackoverflow.com/questions/14712089#comment32440226_14713978 ).
Шон Боб

Аккуратный совет. Относительно того, что этот комментарий не является ответом, по моему мнению, было бы лучше разработать предложение, чтобы выполнить критерии приемлемости для ответа. Я думаю, что при таком подходе может быть достигнуто элегантное решение вопроса.
Эми Пеллегрини

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

1
мы можем знать, какое свойство изменилось?
Эшвин

21

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


5
Тем не менее, $ watchCollection наблюдает только поверхностно. Насколько я понимаю, изменение свойств элементов массива (как в вопросе) не вызовет событие.
Tarnschaf

3
Это должен быть комментарий, а не ответ.
Blazemonger

18

Вот сравнение трех способов просмотра переменной области с примерами:

$ 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) вызывается ВСЕМ выше И:

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

ТОЛЬКО ОДИН БОЛЬШЕ ...

$ Часы () является единственным, который срабатывает, когда массив заменяется другим массивом, даже если этот другой массив имеет точно такое же содержимое.

Например, где $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/


Точно, особенно отметить «ОДНА БОЛЬШЕ»
Энди Ма

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
ОП указал массив объектов. Ваш пример работает с массивом строк, но $ watchCollection не работает с массивом объектов.
KevinL

4

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

scope. $ watch (attrs.testWatch, function () {.....}, 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

Пиран отвечает на это достаточно хорошо и упоминает $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.

НЕ используйте свойство $watchon 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>

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.