Come guardare in profondità un array in angularjs?


320

C'è una serie di oggetti nel mio ambito, voglio vedere tutti i valori di ogni oggetto.

Questo è il mio codice:

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

Ma quando modifico i valori, ad es. Cambio TITLEa TITLE2, il alert('columns changed')mai spuntato.

Come osservare in profondità gli oggetti all'interno di un array?

Esiste una demo live: http://jsfiddle.net/SYx9b/

Risposte:


529

È possibile impostare il terzo argomento di $watcha true:

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

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

A partire da Angular 1.1.x puoi anche usare $ watchCollection per guardare un orologio superficiale (solo il "primo livello" della) collezione.

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

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


2
Perché stai usando angular.equalsquando il terzo argomento assume un valore booleano ?
JayQuerie.com,

Grazie Trevor, lettura errata dei documenti. Ho aggiornato il codice sopra e aggiornato il mio codice affinché corrisponda.
Pirano,

36
in 1.1.x ora hai$watchCollection
Jonathan Rowny il

39
$watchCollectionguarderò solo il "primo livello" di un array o di un oggetto, come ho capito. La risposta sopra è corretta se hai bisogno di guardare più in profondità. bennadel.com/blog/…
Blazemonger

1
Ottima risposta ... ti avrei dato più di un voto positivo se potessi ... :) grazie
Jony-Y,

50

Ci sono conseguenze sulle prestazioni nell'immergere un oggetto nel tuo $ watch. A volte (ad esempio, quando le modifiche sono solo push e pop), potresti voler guardare $ un valore facilmente calcolabile, come array.length.


1
Questo dovrebbe avere più voti. L'osservazione in profondità è costosa. Capisco che OP stava cercando un deep watch, ma le persone potrebbero venire qui solo per sapere se l'array stesso è cambiato. Guardare la lunghezza è molto più veloce, in quel caso.
Scott Silvi,

42
Questo dovrebbe essere un commento, non una risposta.
Blazemonger,

1
I problemi di prestazioni sarebbero minimizzati con l'uso $ watchCollection, come detto da @Blazemonger commento ( stackoverflow.com/questions/14712089#comment32440226_14713978 ).
Sean the Bean,

Consiglio accurato. Considerando che questo commento non è una risposta, secondo me sarebbe meglio elaborare il suggerimento per soddisfare i criteri di accettazione di una risposta. Penso che con questo approccio si possa ottenere una soluzione elegante alla domanda.
Amy Pellegrini,

43

Se guarderai solo un array, puoi semplicemente usare questo bit di codice:

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

esempio

Ma questo non funzionerà con più array:

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

esempio

Per gestire questa situazione, di solito converto le matrici multiple che voglio guardare in JSON:

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

esempio

Come ha sottolineato @jssebastian nei commenti, JSON.stringifypuò essere preferibile in angular.toJsonquanto può gestire membri che iniziano con '$' e anche altri casi possibili.


Trovo anche che esista un terzo parametro $watch, è in grado di fare lo stesso? Pass true as a third argument to watch an object's properties too.Vedi: cheatography.com/proloser/cheat-sheets/angularjs
Freewind

@Freewind Se si verificherà mai un caso in cui sarà necessario guardare più array, fallirà come visto qui . Ma sì, funzionerà anche questo e fornirà le stesse funzionalità dell'uso angular.toJsonsu un singolo array.
JayQuerie.com,

2
Fai attenzione che angular.toJson non sembra includere i membri include che iniziano con '$': angular.toJson ({"$ hello": "world"}) è solo "{}". Ho usato JSON.stringify () come alternativa
jssebastian il

@ Grazie jssebastian - Ho aggiornato la risposta per includere tali informazioni.
JayQuerie.com,

1
possiamo sapere quale proprietà è cambiata?
Ashwin,

21

Vale la pena notare che in Angular 1.1.xe versioni successive, è ora possibile utilizzare $ watchCollection anziché $ watch. Sebbene $ watchCollection sembri creare orologi poco profondi in modo da non funzionare con matrici di oggetti come previsto. Può rilevare aggiunte ed eliminazioni nell'array, ma non le proprietà degli oggetti all'interno degli array.


5
$ watchCollection guarda solo superficialmente, tuttavia. Quindi, come ho capito, la modifica delle proprietà degli elementi dell'array (come nella domanda) non attiverebbe l'evento.
Tarnschaf,

3
Questo dovrebbe essere un commento, non una risposta.
Blazemonger,

18

Ecco un confronto dei 3 modi in cui è possibile guardare una variabile ambito con esempi:

$ watch () è attivato da:

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

$ watchCollection () è attivato da tutto quanto sopra E:

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

$ watch (..., true) è attivato da TUTTO sopra E:

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

SOLAMENTE UN'ALTRA COSA...

$ watch () è l'unico che si innesca quando un array viene sostituito con un altro array anche se quell'altro array ha lo stesso contenuto esatto.

Ad esempio, dove $watch()sparerebbe e $watchCollection()non dovrebbe:

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

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

$scope.myArray = newArray;

Di seguito è riportato un collegamento a un JSFiddle di esempio che utilizza tutte le diverse combinazioni di orologi e genera messaggi di registro per indicare quali "orologi" sono stati attivati:

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


Esatto, soprattutto per notare "UNA COSA PIÙ"
Andy Ma

12

$ watchCollection realizza ciò che vuoi fare. Di seguito è riportato un esempio copiato dal sito Web angularjs http://docs.angularjs.org/api/ng/type/$rootScope.Scope Sebbene sia conveniente, le prestazioni devono essere prese in considerazione soprattutto quando si guarda una grande collezione.

  $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 ha specificato una matrice di oggetti. Il tuo esempio funziona con una matrice di stringhe, ma $ watchCollection non funziona con una matrice di oggetti.
KevinL

4

Questa soluzione ha funzionato molto bene per me, lo sto facendo in una direttiva:

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

il vero funziona abbastanza bene e reagisce per tutti i chnages (aggiungi, cancella o modifica un campo).

Ecco un plunker funzionante per giocarci.

Profondamente guardando una matrice in AngularJS

Spero che questo possa esserti utile. Se hai domande, sentiti libero di chiedere, proverò ad aiutarti :)


4

Nel mio caso, avevo bisogno di guardare un servizio, che contiene un oggetto indirizzo controllato anche da molti altri controller. Sono rimasto bloccato in un ciclo fino a quando non ho aggiunto il parametro "vero", che sembra essere la chiave del successo quando si osservano oggetti.

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

1

L'impostazione del objectEqualityparametro (terzo parametro) della $watchfunzione è sicuramente il modo corretto di controllare TUTTE le proprietà dell'array.

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

Piran risponde abbastanza bene e menziona $watchCollectionanche.

Più dettaglio

Il motivo per cui sto rispondendo a una domanda già risposta è perché voglio sottolineare che la risposta di Wizardwerdna non è buona e non dovrebbe essere usata.

Il problema è che i digest non avvengono immediatamente. Devono attendere il completamento dell'attuale blocco di codice prima di eseguire. Quindi, guardare il lengthdi un array potrebbe effettivamente mancare alcune importanti modifiche che $watchCollectioncattureranno.

Supponi questa configurazione:

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

A prima vista, potrebbe sembrare che questi sparerebbero contemporaneamente, come in questo caso:

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

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

Funziona abbastanza bene, ma considera questo:

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

// Console output
// testArray changed

Si noti che la lunghezza risultante era la stessa anche se l'array ha un nuovo elemento e ha perso un elemento, quindi, per quanto riguarda l'orologio $watch, lengthnon è cambiato. $watchCollectionraccolto su di esso, però.

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

// Console output
// testArray change

Lo stesso risultato si verifica con un push e pop nello stesso blocco.

Conclusione

Per guardare tutte le proprietà dell'array, utilizzare $watcha sull'array stesso con il terzo parametro (objectEquality) incluso e impostato su true. Sì, questo è costoso ma a volte necessario.

Per vedere quando l'oggetto entra / esce dall'array, usare a $watchCollection.

NON utilizzare a $watchsulla lengthproprietà dell'array. Non c'è quasi nessuna buona ragione a cui posso pensare di farlo.


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>

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.