Wie kann man ein Array in Angularjs genau beobachten?

320

In meinem Bereich befindet sich eine Reihe von Objekten. Ich möchte alle Werte jedes Objekts überwachen.

Das ist mein Code:

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

Aber wenn ich die Werte ändere, z. B. zu ändere TITLE, TITLE2ist das alert('columns changed')nie aufgetaucht.

Wie kann man die Objekte in einem Array genau beobachten?

Es gibt eine Live-Demo: http://jsfiddle.net/SYx9b/

Freilauf
quelle

Antworten:

529

Sie können das 3. Argument $watchauf setzen true:

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

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

Seit Angular 1.1.x können Sie auch $ watchCollection verwenden, um die flache Uhr (nur die "erste Ebene") der Sammlung anzusehen.

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

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

Piran
quelle
2
Warum verwenden Sie, angular.equalswenn das dritte Argument einen booleschen Wert annimmt ?
JayQuerie.com
Vielen Dank, Trevor, falsches Lesen der Dokumente. Ich habe den obigen Code aktualisiert und meinen Code entsprechend aktualisiert.
Piran
36
in 1.1.x haben Sie jetzt$watchCollection
Jonathan Rowny
39
$watchCollectionIch werde nur die "erste Ebene" eines Arrays oder Objekts beobachten, so wie ich es verstehe. Die obige Antwort ist richtig, wenn Sie tiefer schauen müssen. bennadel.com/blog/…
Blazemonger
1
Tolle Antwort ... hätte dir mehr als eine Stimme gegeben, wenn ich könnte ... :) Danke
Jony-Y
50

Das Tieftauchen eines Objekts in Ihrer $ watch hat Leistungsfolgen. Manchmal (z. B. wenn Änderungen nur Pushs und Pops sind) möchten Sie möglicherweise einen leicht zu berechnenden Wert wie array.length $ beobachten.

wizardwerdna
quelle
1
Dies sollte mehr Stimmen haben. Deep Watching ist teuer. Ich verstehe, dass OP nach tiefer Beobachtung suchte, aber die Leute könnten hierher kommen, um nur zu wissen, ob sich das Array selbst geändert hat. In diesem Fall ist es viel schneller, die Länge zu beobachten.
Scott Silvi
42
Dies sollte ein Kommentar sein, keine Antwort.
Blazemonger
1
Die Leistungsprobleme würden durch die Verwendung von $ watchCollection minimiert, wie im Kommentar @Blazemonger ( stackoverflow.com/questions/14712089#comment32440226_14713978 ) erwähnt.
Sean the Bean
Ordentlicher Rat. Da dieser Kommentar keine Antwort ist, ist es meiner Meinung nach besser, den Vorschlag auszuarbeiten, um die Akzeptanzkriterien für eine Antwort zu erfüllen. Ich denke, dass mit diesem Ansatz eine elegante Lösung für die Frage erreicht werden kann.
Amy Pellegrini
43

Wenn Sie nur ein Array ansehen möchten, können Sie einfach diesen Code verwenden:

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

Beispiel

Dies funktioniert jedoch nicht mit mehreren Arrays:

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

Beispiel

Um diese Situation zu bewältigen, konvertiere ich normalerweise die mehreren Arrays, die ich beobachten möchte, in JSON:

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

Beispiel

Wie @jssebastian in den Kommentaren hervorhob, ist dies JSON.stringifymöglicherweise vorzuziehenangular.toJson da es Mitglieder behandeln kann, die mit '$' beginnen, sowie mögliche andere Fälle.

JayQuerie.com
quelle
Ich finde auch, dass es einen dritten Parameter $watchgibt, kann er dasselbe tun? Pass true as a third argument to watch an object's properties too.Siehe: cheatography.com/proloser/cheat-sheets/angularjs
Freewind
@Freewind Wenn es jemals einen Fall gibt, in dem Sie mehrere Arrays beobachten müssen, schlägt dies fehl, wie hier gezeigt . Aber ja, das funktioniert auch und bietet die gleiche Funktionalität wie die Verwendung angular.toJsonauf einem einzelnen Array.
JayQuerie.com
2
Beachten Sie jedoch, dass angular.toJson keine Include-Mitglieder enthält, die mit '$' beginnen: angular.toJson ({"$ hello": "world"}) ist nur "{}". Ich habe JSON.stringify () als Alternative verwendet
jssebastian
@ jssebastian Danke - Ich habe die Antwort aktualisiert, um diese Informationen aufzunehmen.
JayQuerie.com
1
Können wir wissen, welche Eigenschaft sich geändert hat?
Ashwin
21

Es ist erwähnenswert, dass Sie in Angular 1.1.x und höher jetzt $ watchCollection anstelle von $ watch verwenden können. Obwohl die $ watchCollection flache Uhren zu erstellen scheint, funktioniert sie nicht mit Arrays von Objekten, wie Sie es erwarten. Es kann Hinzufügungen und Löschungen zum Array erkennen, jedoch nicht die Eigenschaften von Objekten in Arrays.

Jonathan Rowny
quelle
5
$ watchCollection sieht jedoch nur flach zu. So wie ich es verstehe, würde das Ändern der Eigenschaften der Elemente des Arrays (wie in der Frage) das Ereignis nicht auslösen.
Tarnschaf
3
Dies sollte ein Kommentar sein, keine Antwort.
Blazemonger
18

Hier ist ein Vergleich der drei Möglichkeiten, wie Sie eine Bereichsvariable mit Beispielen anzeigen können:

$ watch () wird ausgelöst durch:

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

$ watchCollection () wird durch alles über AND ausgelöst:

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

$ watch (..., true) wird durch ALLES über AND ausgelöst:

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

NUR NOCH EINE SACHE...

$ watch () ist die einzige, die ausgelöst wird, wenn ein Array durch ein anderes Array ersetzt wird, selbst wenn dieses andere Array denselben exakten Inhalt hat.

Zum Beispiel, wo $watch()würde feuern und $watchCollection()nicht:

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

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

$scope.myArray = newArray;

Unten finden Sie einen Link zu einem Beispiel für JSFiddle, das alle verschiedenen Uhrenkombinationen verwendet und Protokollmeldungen ausgibt, um anzugeben, welche "Uhren" ausgelöst wurden:

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

Luis Perez
quelle
Genau, besonders um das "EINE MEHR DING" zu beachten
Andy Ma
12

$ watchCollection erreicht, was Sie tun möchten. Unten finden Sie ein Beispiel, das von der AngularJS-Website http://docs.angularjs.org/api/ng/type/$rootScope.Scope kopiert wurde. Obwohl dies praktisch ist, muss die Leistung berücksichtigt werden, insbesondere wenn Sie eine große Sammlung ansehen.

  $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
quelle
14
OP hat ein Array von Objekten angegeben. Ihr Beispiel funktioniert mit einem Array von Zeichenfolgen, aber $ watchCollection funktioniert nicht mit einem Array von Objekten.
KevinL
4

Diese Lösung hat bei mir sehr gut funktioniert, ich mache das in einer Direktive:

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

Das wahre funktioniert ziemlich gut und reagiert auf alle Änderungen (Hinzufügen, Löschen oder Ändern eines Feldes).

Hier ist ein funktionierender Plunker zum Spielen.

Ein Array in AngularJS genau beobachten

Ich hoffe, das kann für Sie nützlich sein. Wenn Sie Fragen haben, zögern Sie nicht zu fragen, ich werde versuchen zu helfen :)

EPotignano
quelle
4

In meinem Fall musste ich einen Dienst überwachen, der ein Adressobjekt enthält, das auch von mehreren anderen Controllern überwacht wird. Ich steckte in einer Schleife fest, bis ich den Parameter 'true' hinzufügte, der der Schlüssel zum Erfolg beim Betrachten von Objekten zu sein scheint.

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

Das Einstellen des objectEqualityParameters (dritter Parameter) der $watchFunktion ist definitiv die richtige Methode, um ALLE Eigenschaften des Arrays zu überwachen.

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

Piran antwortet gut genug und erwähnt es $watchCollectionauch.

Mehr Details

Der Grund, warum ich eine bereits beantwortete Frage beantworte, ist, dass ich darauf hinweisen möchte, dass die Antwort von wizardwerdna nicht gut ist und nicht verwendet werden sollte.

Das Problem ist, dass die Digests nicht sofort stattfinden. Sie müssen warten, bis der aktuelle Codeblock vollständig ist, bevor sie ausgeführt werden. Beobachten Sie daher, wie das lengthArray tatsächlich einige wichtige Änderungen übersieht, $watchCollectiondie abfangen werden.

Nehmen Sie diese Konfiguration an:

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

Auf den ersten Blick scheint es, als würden diese gleichzeitig feuern, wie in diesem Fall:

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

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

Das funktioniert gut genug, aber bedenken Sie Folgendes:

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

// Console output
// testArray changed

Beachten Sie, dass die resultierende Länge war die gleiche , obwohl das Array ein neues Element hat und verloren ein Element, um zusehen , wie das $watchgeht, lengthhat sich nicht geändert. $watchCollectionnahm es jedoch auf.

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

// Console output
// testArray change

Das gleiche Ergebnis wird mit einem Push and Pop im selben Block erzielt.

Fazit

Um jede Eigenschaft im Array zu überwachen, verwenden Sie a $watchfür das Array selbst, wobei der dritte Parameter (objectEquality) enthalten ist und auf true gesetzt ist. Ja, das ist teuer, aber manchmal notwendig.

Verwenden Sie a, um zu beobachten, wann ein Objekt das Array betritt / verlässt $watchCollection.

Verwenden Sie NICHT a $watchfür die lengthEigenschaft des Arrays. Es gibt fast keinen guten Grund, warum ich mir das vorstellen kann.

Patrick John Haskins
quelle
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
quelle