Як глибоко переглядати масив у 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

Ви можете встановити 3 - ий аргумент $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
Це повинно мати більше голосів. Глибоке перегляд дорого коштує. Я розумію, що ОП шукала глибокого спостереження, але люди можуть прийти сюди просто бажаючи знати, чи змінився сам масив. Спостерігати за довжиною набагато швидше.
Скотт Сілві

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, схоже, не включає членів, які починаються з '$': angular.toJson ({"$ hello": "world"}) - це просто "{}". Я використовував JSON.stringify () як альтернативу
jssebastian

@ jssebastian Дякую - я оновив відповідь, щоб включити цю інформацію.
JayQuerie.com

1
чи можемо ми знати, яка власність змінилася?
Ешвін

21

Варто зазначити, що в Angular 1.1.x і вище ви тепер можете використовувати $ watchCollection, а не $ watch. Хоча здається, що $ watchCollection створює неглибокі годинники, тому він не працюватиме з масивами об'єктів, як ви очікуєте. Він може виявляти доповнення та видалення масиву, але не властивості об'єктів всередині масивів.


5
$ watchCollection, однак, дивиться лише неглибоко. Тож, як я розумію, зміна властивостей елементів масиву (як у питанні) не призведе до події.
Тарншаф

3
Це має бути коментар, а не відповідь.
Blazemonger

18

Ось порівняння трьох способів перегляду змінної області із прикладами:

$ watch () спрацьовує:

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

$ watchCollection () спрацьовує все вище І:

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

$ watch (..., вірно) спрацьовує ВСЕ ВСЕ вище та:

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


Точно, особливо відзначте «ОДНО БІЛЬШЕ»,
Енді Ма

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

4

Це рішення спрацювало для мене дуже добре, я роблю це в директиві:

$ watch (attrs.testWatch, function () {.....}, вірно);

справжній працює досить добре і реагує на всі канали (додавання, видалення чи зміна поля).

Ось робочий планкер для гри з ним.

Глибоко переглядаючи масив в 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

Той самий результат відбувається при натисканні і попсі в одному блоці.

Висновок

Для перегляду кожної властивості масиву використовуйте a $watchна масиві з включеним третім параметром (objectEquality) та встановіть значення true. Так, це дорого, але іноді необхідно.

Щоб спостерігати, коли об’єкт входить / виходить з масиву, використовуйте a $watchCollection.

НЕ використовуйте a $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>

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.