Jak głęboko obejrzeć tablicę w angularjs?

320

W moim zakresie jest tablica obiektów, chcę oglądać wszystkie wartości każdego obiektu.

To jest mój kod:

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

Ale kiedy modyfikuję wartości, np. Zmieniam TITLEna TITLE2, alert('columns changed')nigdy nie pojawił się.

Jak głęboko obserwować obiekty w tablicy?

Istnieje demo na żywo: http://jsfiddle.net/SYx9b/

Freewind
źródło

Odpowiedzi:

529

Możesz ustawić trzeci argument $watchna true:

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

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

Od wersji Angular 1.1.x możesz także używać $ watchCollection do oglądania płytkiego oglądania (tylko „pierwszego poziomu”) kolekcji.

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

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

Piran
źródło
2
Dlaczego używasz, angular.equalsgdy trzeci argument przyjmuje wartość logiczną ?
JayQuerie.com,
Dzięki Trevor, błędne odczytanie dokumentów. Zaktualizowałem powyższy kod i zaktualizowałem mój kod, aby pasował.
Piran
36
w 1.1.x teraz masz$watchCollection
Jonathan Rowny
39
$watchCollectionbędzie oglądać tylko „pierwszy poziom” tablicy lub obiektu, tak jak to rozumiem. Powyższa odpowiedź jest poprawna, jeśli chcesz oglądać głębiej. bennadel.com/blog/…
Blazemonger
1
Świetna odpowiedź ... dałbym ci więcej niż jeden głos, gdybym mógł ... :) dzięki
Jony-Y
50

Głębokie nurkowanie na obiekcie w zegarku ma wpływ na wydajność. Czasami (na przykład, gdy zmiany są popychaniami i wyskakuje), możesz chcieć obejrzeć łatwo obliczalną wartość, na przykład array.length.

wizardwerdna
źródło
1
To powinno mieć więcej głosów. Głębokie oglądanie jest drogie. Rozumiem, że OP szukało głębokiego oglądania, ale ludzie mogą przyjechać tutaj, chcąc wiedzieć, czy sama tablica się zmieniła. W takim przypadku oglądanie długości jest znacznie szybsze.
Scott Silvi,
42
To powinien być komentarz, a nie odpowiedź.
Blazemonger,
1
Problemy z wydajnością można zminimalizować za pomocą $ watchCollection, jak wspomniano w komentarzu @Blazemonger ( stackoverflow.com/questions/14712089#comment32440226_14713978 ).
Sean the Bean
Zgrabna rada. Biorąc pod uwagę, że ten komentarz nie jest odpowiedzią, moim zdaniem lepiej byłoby rozwinąć sugestię, aby spełnić kryteria akceptacji odpowiedzi. Myślę, że dzięki takiemu podejściu można uzyskać eleganckie rozwiązanie tego pytania.
Amy Pellegrini,
43

Jeśli zamierzasz oglądać tylko jedną tablicę, możesz po prostu użyć tego fragmentu kodu:

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

przykład

Ale to nie będzie działać z wieloma tablicami:

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

przykład

Aby poradzić sobie z tą sytuacją, zwykle przekształcam wiele tablic, które chcę oglądać, w JSON:

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

przykład

Jak zauważył @jssebastian w komentarzach, JSON.stringifymoże być lepszym rozwiązaniem, angular.toJsonponieważ może obsługiwać członków zaczynających się na „$” i możliwe inne przypadki.

JayQuerie.com
źródło
Uważam również, że istnieje trzeci parametr $watch, czy można zrobić to samo? Pass true as a third argument to watch an object's properties too.Zobacz: cheatography.com/proloser/cheat-sheets/angularjs
Freewind
@Freewind Jeśli zdarzy się przypadek, w którym będziesz musiał obejrzeć wiele tablic, zakończy się niepowodzeniem, jak pokazano tutaj . Ale tak, to też zadziała i zapewnia taką samą funkcjonalność, jak angular.toJsonw przypadku pojedynczej tablicy.
JayQuerie.com,
2
Uważaj tylko, że angular.toJson nie obejmuje członków zaczynających się na „$”: angular.toJson ({„$ hello”: „world”}) to po prostu „{}”. Jako alternatywy użyłem JSON.stringify ()
jssebastian
@ jssebastian Dzięki - zaktualizowałem odpowiedź, aby uwzględnić te informacje.
JayQuerie.com,
1
czy możemy wiedzieć, która nieruchomość uległa zmianie?
Ashwin
21

Warto zauważyć, że w Angular 1.1.x i nowszych możesz teraz używać $ watchCollection zamiast $ watch. Chociaż $ watchCollection wydaje się tworzyć płytkie zegarki, więc nie będzie działać z tablicami obiektów, jak się spodziewasz. Może wykrywać dodawanie i usuwanie do tablicy, ale nie właściwości obiektów wewnątrz tablic.

Jonathan Rowny
źródło
5
$ watchCollection zegarki tylko płytkie. Tak więc, jak rozumiem, zmiana właściwości elementów tablicy (jak w pytaniu) nie spowodowałaby zdarzenia.
Tarnschaf
3
To powinien być komentarz, a nie odpowiedź.
Blazemonger,
18

Oto porównanie 3 sposobów oglądania zmiennej zasięgu z przykładami:

$ watch () jest uruchamiany przez:

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

$ watchCollection () jest wywoływane przez wszystko powyżej ORAZ:

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

$ watch (..., true) jest wyzwalany przez WSZYSTKO powyżej ORAZ:

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

JESZCZE JEDNA RZECZ...

$ watch () jest jedynym, który uruchamia się, gdy tablica jest zastępowana inną tablicą, nawet jeśli ta inna tablica ma taką samą dokładną treść.

Na przykład, gdzie $watch()strzelałby i $watchCollection()nie:

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

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

$scope.myArray = newArray;

Poniżej znajduje się link do przykładowego JSFiddle, który używa wszystkich różnych kombinacji zegarków i generuje komunikaty dziennika, aby wskazać, które „zegarki” zostały uruchomione:

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

Luis Perez
źródło
Dokładnie, szczególnie, aby zauważyć „JEDEN WIĘCEJ RZECZY”
Andy Ma
12

$ watchCollection realizuje to, co chcesz zrobić. Poniżej znajduje się przykład skopiowany ze strony internetowej angularjs http://docs.angularjs.org/api/ng/type/$rootScope.Scope Chociaż jest to wygodne, wydajność należy wziąć pod uwagę, szczególnie podczas oglądania dużej kolekcji.

  $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);
Jin
źródło
14
OP określił tablicę obiektów. Twój przykład działa z tablicą ciągów, ale $ watchCollection nie działa z tablicą obiektów.
KevinL
4

To rozwiązanie działało dla mnie bardzo dobrze, robię to w dyrektywie:

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

prawda działa całkiem dobrze i reaguje na wszystkie porcje (dodawanie, usuwanie lub modyfikowanie pola).

Oto działający plunker do zabawy.

Dogłębne obserwowanie tablicy w AngularJS

Mam nadzieję, że może ci się to przydać. Jeśli masz jakieś pytania, nie krępuj się zapytać, postaram się pomóc :)

EPotignano
źródło
4

W moim przypadku musiałem obejrzeć usługę, która zawiera obiekt adresu obserwowany również przez kilka innych kontrolerów. Utknąłem w pętli, dopóki nie dodałem parametru „true”, który wydaje się być kluczem do sukcesu podczas oglądania obiektów.

$scope.$watch(function() {
    return LocationService.getAddress();
}, function(address) {
    //handle address object
}, true);
RevNoah
źródło
1

Ustawienie objectEqualityparametru (trzeciego parametru) $watchfunkcji jest zdecydowanie poprawnym sposobem oglądania WSZYSTKICH właściwości tablicy.

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

Piran odpowiada na to wystarczająco dobrze i również wspomina $watchCollection.

Więcej szczegółów

Powodem, dla którego odpowiadam na pytanie, na które już udzielono odpowiedzi, jest to, że chcę podkreślić, że odpowiedź wizardwerdna nie jest dobra i nie należy jej używać.

Problem polega na tym, że trawienia nie następują natychmiast. Muszą poczekać na zakończenie bieżącego bloku kodu przed wykonaniem. Dlatego obserwuj lengthtablicę, która może faktycznie przegapić kilka ważnych zmian, które $watchCollectionzostaną przechwycone.

Załóż tę konfigurację:

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

Na pierwszy rzut oka może się wydawać, że strzelałyby jednocześnie, tak jak w tym przypadku:

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

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

To działa wystarczająco dobrze, ale rozważ to:

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

// Console output
// testArray changed

Zauważ, że wynikowa długość była taka sama, mimo że tablica ma nowy element i straciła element, więc jeśli chodzi o obserwację $watch, lengthnie zmieniła się. $watchCollectionjednak to zrozumiałam.

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

// Console output
// testArray change

Ten sam rezultat dzieje się z naciśnięciem i popem w tym samym bloku.

Wniosek

Aby obejrzeć każdą właściwość w tablicy, użyj $watchsamej tablicy z trzecim parametrem (objectEquality) zawartym i ustawionym na true. Tak, jest to drogie, ale czasem konieczne.

Aby obserwować, kiedy obiekt wchodzi / wychodzi z tablicy, użyj $watchCollection.

NIE używaj a $watchwe lengthwłaściwości tablicy. Nie ma prawie żadnego dobrego powodu, aby to zrobić.

Patrick John Haskins
źródło
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>

Fluke Klumame
źródło