¿Cómo mirar en profundidad una matriz en angularjs?


320

Hay una variedad de objetos en mi alcance, quiero ver todos los valores de cada objeto.

Este es mi código:

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

Pero cuando modifico los valores, por ejemplo, cambio TITLEa TITLE2, alert('columns changed')nunca aparece.

¿Cómo mirar profundamente los objetos dentro de una matriz?

Hay una demostración en vivo: http://jsfiddle.net/SYx9b/

Respuestas:


529

Puede establecer el tercer argumento de $watcha true:

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

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

Desde Angular 1.1.x también puede usar $ watchCollection para ver el reloj superficial (solo el "primer nivel" de) la colección.

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

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


2
¿Por qué estás usando angular.equalscuando el tercer argumento toma un valor booleano ?
JayQuerie.com

Gracias Trevor, mala lectura de los documentos. He actualizado el código anterior y actualicé mi código para que coincida.
Piran

36
en 1.1.x ahora tienes$watchCollection
Jonathan Rowny

39
$watchCollectionsolo veré el "primer nivel" de una matriz u objeto, según tengo entendido. La respuesta anterior es correcta si necesita mirar más profundo que eso. bennadel.com/blog/…
Blazemonger

1
Gran respuesta ... te hubiera dado más de un voto si pudiera ... :) gracias
Jony-Y

50

Hay consecuencias de rendimiento al sumergirse en un objeto en su $ watch. A veces (por ejemplo, cuando los cambios son solo empujones y estallidos), es posible que desee $ mirar un valor fácilmente calculado, como array.length.


1
Esto debería tener más votos. La observación profunda es cara. Entiendo que OP estaba buscando una observación profunda, pero la gente podría venir aquí solo queriendo saber si la matriz en sí misma cambió. Ver la longitud es mucho más rápido, en ese caso.
Scott Silvi

42
Esto debería ser un comentario, no una respuesta.
Blazemonger

1
Los problemas de rendimiento se minimizarían utilizando $ watchCollection, como se menciona en el comentario de @Blazemonger ( stackoverflow.com/questions/14712089#comment32440226_14713978 ).
Sean the Bean

Buen consejo. Con respecto a que este comentario no es una respuesta, en mi opinión sería mejor elaborar la sugerencia para cumplir con los criterios de aceptación de una respuesta. Creo que con este enfoque se puede lograr una solución elegante a la pregunta.
Amy Pellegrini

43

Si va a ver solo una matriz, simplemente puede usar este bit de código:

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

ejemplo

Pero esto no funcionará con múltiples matrices:

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

ejemplo

Para manejar esta situación, generalmente convierto las múltiples matrices que quiero ver en JSON:

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

ejemplo

Como @jssebastian señaló en los comentarios, JSON.stringifypuede ser preferible angular.toJsonya que puede manejar miembros que comienzan con '$' y otros posibles casos también.


También encuentro que hay un tercer parámetro para $watch, ¿es capaz de hacer lo mismo? Pass true as a third argument to watch an object's properties too.Ver: cheatography.com/proloser/cheat-sheets/angularjs
Freewind

@Freewind Si alguna vez hay un caso en el que necesitará ver múltiples matrices, fallará como se ve aquí . Pero sí, eso también funcionará y proporciona la misma funcionalidad que usar angular.toJsonen una sola matriz.
JayQuerie.com

2
Solo tenga en cuenta que angular.toJson no parece incluir miembros incluidos que comienzan con '$': angular.toJson ({"$ hello": "world"}) es solo "{}". Usé JSON.stringify () como alternativa
jssebastian

@ jssebastian Gracias. He actualizado la respuesta para incluir esa información.
JayQuerie.com

1
¿Podemos saber qué propiedad ha cambiado?
Ashwin

21

Vale la pena señalar que en Angular 1.1.xy superior, ahora puede usar $ watchCollection en lugar de $ watch. Aunque $ watchCollection parece crear relojes poco profundos, por lo que no funcionará con conjuntos de objetos como usted espera. Puede detectar adiciones y eliminaciones a la matriz, pero no las propiedades de los objetos dentro de las matrices.


55
$ watchCollection solo observa relojes poco profundos, sin embargo. Entonces, según tengo entendido, cambiar las propiedades de los elementos de la matriz (como en la pregunta) no desencadenaría el evento.
Tarnschaf

3
Esto debería ser un comentario, no una respuesta.
Blazemonger

18

Aquí hay una comparación de las 3 formas en que puede ver una variable de alcance con ejemplos:

$ watch () se activa por:

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

$ watchCollection () se activa por todo lo anterior Y:

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

$ watch (..., true) se activa por TODO lo anterior Y:

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

SOLO UNA COSA MÁS...

$ watch () es el único que se activa cuando una matriz se reemplaza por otra matriz, incluso si esa otra matriz tiene el mismo contenido exacto.

Por ejemplo, dónde $watch()se dispararía y $watchCollection()no:

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

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

$scope.myArray = newArray;

A continuación hay un enlace a un ejemplo de JSFiddle que utiliza todas las diferentes combinaciones de relojes y envía mensajes de registro para indicar qué "relojes" se activaron:

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


Exactamente, especialmente para notar la 'UNA COSA MÁS'
Andy Ma

12

$ watchCollection logra lo que quieres hacer. A continuación se muestra un ejemplo copiado del sitio web de angularjs http://docs.angularjs.org/api/ng/type/$rootScope.Scope Si bien es conveniente, el rendimiento debe tenerse en cuenta especialmente cuando mira una gran colección.

  $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 especificó una matriz de objetos. Su ejemplo funciona con una matriz de cadenas, pero $ watchCollection no funciona con una matriz de objetos.
KevinL

4

Esta solución funcionó muy bien para mí, estoy haciendo esto en una directiva:

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

Lo verdadero funciona bastante bien y reacciona para todos los cambios (agregar, eliminar o modificar un campo).

Aquí hay un saqueador para jugar con él.

Mirando profundamente una matriz en AngularJS

Espero que esto te sea útil. Si tiene alguna pregunta, no dude en preguntar, intentaré ayudar :)


4

En mi caso, necesitaba ver un servicio, que contiene un objeto de dirección también observado por varios otros controladores. Estaba atrapado en un bucle hasta que agregué el parámetro 'verdadero', que parece ser la clave del éxito al mirar objetos.

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

1

Definir el objectEqualityparámetro (tercer parámetro) de la $watchfunción es definitivamente la forma correcta de ver TODAS las propiedades de la matriz.

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

Piran responde esto bastante bien y menciona $watchCollectiontambién.

Mas detalle

La razón por la que estoy respondiendo una pregunta ya respondida es porque quiero señalar que la respuesta de wizardwerdna no es buena y no debe usarse.

El problema es que los resúmenes no ocurren de inmediato. Tienen que esperar hasta que el bloque de código actual se haya completado antes de ejecutar. Por lo tanto, ver cómo lengthuna matriz puede perder algunos cambios importantes que $watchCollectionatraparán.

Asuma esta configuración:

$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 primera vista, puede parecer que se dispararían al mismo tiempo, como en este caso:

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

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

Eso funciona bastante bien, pero considera esto:

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

// Console output
// testArray changed

Tenga en cuenta que la longitud resultante fue la misma a pesar de que la matriz tiene un nuevo elemento y perdió un elemento, por lo que, en lo que $watchrespecta al reloj , lengthno ha cambiado. $watchCollectionSin embargo, lo entendí.

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

// Console output
// testArray change

El mismo resultado ocurre con un push y pop en el mismo bloque.

Conclusión

Para ver todas las propiedades de la matriz, use a $watchen la matriz con el tercer parámetro (objectEquality) incluido y establecido en verdadero. Sí, esto es costoso pero a veces necesario.

Para ver cuándo el objeto entra / sale de la matriz, use a $watchCollection.

NO use a $watchen la lengthpropiedad de la matriz. Casi no hay una buena razón para pensar en hacerlo.


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>

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.