Mire múltiples atributos de $ scope

Respuestas:

585

A partir de AngularJS 1.3 hay un nuevo método llamado $watchGrouppara observar un conjunto de expresiones.

$scope.foo = 'foo';
$scope.bar = 'bar';

$scope.$watchGroup(['foo', 'bar'], function(newValues, oldValues, scope) {
  // newValues array contains the current values of the watch expressions
  // with the indexes matching those of the watchExpression array
  // i.e.
  // newValues[0] -> $scope.foo 
  // and 
  // newValues[1] -> $scope.bar 
});
Paolo Moretti
fuente
77
¿Cuál es la diferencia con $ watchCollection ('[a, b]') ??
Kamil Tomšík
28
La diferencia es que puede usar una matriz adecuada en lugar de una cadena que se parece a una matriz
Paolo Moretti
2
Tuve un problema similar pero usando el patrón controllerAs. En mi caso, la solución consistió en anteponer a las variables el nombre del controlador:$scope.$watchGroup(['mycustomctrl.foo', 'mycustomctrl.bar'],...
morels
1
Esto no parece funcionar para mí en Angular 1.6. Si pongo un registro de consola en la función, puedo ver que solo se ejecuta una vez con newValuesy oldValuescomo undefined, mientras veo individualmente que las propiedades funcionan como de costumbre.
Alejandro García Iglesias
290

Comenzando con AngularJS 1.1.4 puede usar $watchCollection:

$scope.$watchCollection('[item1, item2]', function(newValues, oldValues){
    // do stuff here
    // newValues and oldValues contain the new and respectively old value
    // of the observed collection array
});

Ejemplo de Plunker aquí

Documentación aquí

Răzvan Flavius ​​Panda
fuente
109
Tenga en cuenta que el primer parámetro no es una matriz. Es una cadena que se parece a una matriz.
MK Safi
1
gracias por esto. si me funciona, te daré mil monedas de oro - virtuales, por supuesto :)
AdityaSaxena
55
Para ser claros (me llevó unos minutos darme cuenta), $watchCollectionse pretende que se le entregue un objeto y se proporcione una vigilancia de los cambios en cualquiera de las propiedades del objeto, mientras que $watchGroupse pretende que se le entregue una serie de propiedades individuales para observar los cambios. Ligeramente diferente para abordar problemas similares pero diferentes. ¡Uf!
Mattygabe
1
De alguna manera, newValues ​​y oldValues ​​siempre son iguales cuando usa este método: plnkr.co/edit/SwwdpQcNf78pQ80hhXMr
mostruash
Gracias. Solucionó mi problema. ¿Hay algún problema / problema de rendimiento al usar $ watch?
Syed Nasir Abbas
118

$watch El primer parámetro también puede ser una función.

$scope.$watch(function watchBothItems() {
  return itemsCombinedValue();
}, function whenItemsChange() {
  //stuff
});

Si sus dos valores combinados son simples, el primer parámetro es simplemente una expresión angular normalmente. Por ejemplo, nombre y apellido:

$scope.$watch('firstName + lastName', function() {
  //stuff
});
Andrew Joslin
fuente
8
Esto parece un caso de uso que está pidiendo su inclusión en el marco de forma predeterminada. Incluso una propiedad calculada tan simple como fullName = firstName + " " + lastNamerequiere pasar una función como primer argumento (en lugar de una expresión simple como 'firstName, lastName'). Angular es TAN poderoso, pero hay algunas áreas donde puede ser enormemente más amigable para el desarrollador de manera que no (parece) comprometer el rendimiento o los principios de diseño.
XML
44
Bueno, $ watch solo toma una expresión angular, que se evalúa en el ámbito en el que se llama. Entonces podrías hacer $scope.$watch('firstName + lastName', function() {...}). También puede simplemente hacer dos relojes: function nameChanged() {}; $scope.$watch('firstName', nameChanged); $scope.$watch('lastName', nameChanged);. Agregué el caso simple a la respuesta.
Andrew Joslin
La ventaja en 'firstName + lastName' es la concatenación de cadenas. Entonces angular solo comprueba si el resultado de la concatenación es diferente ahora.
mb21
16
La concatenación de cadenas requiere un poco de cuidado: si cambia "paran orman" a "para norman", no activará una respuesta; mejor poner un divisor como "\ t | \ t" entre el primer y el segundo término, o simplemente apegarse a JSON, que es definitivo
Dave Edelhart
aunque amberjs apesta, ¡pero tiene esta característica incorporada! escucha eso angulosos chicos. Supongo que hacer relojes es la forma más racional posible.
Aladdin Mhemed
70

Aquí hay una solución muy similar a su pseudocódigo original que realmente funciona:

$scope.$watch('[item1, item2] | json', function () { });

EDITAR: Bien, creo que esto es aún mejor:

$scope.$watch('[item1, item2]', function () { }, true);

Básicamente, estamos omitiendo el paso json, que parecía tonto al principio, pero no estaba funcionando sin él. La clave es el tercer parámetro a menudo omitido que activa la igualdad de objetos en lugar de la igualdad de referencia. Entonces, las comparaciones entre nuestros objetos de matriz creados realmente funcionan bien.

Karen Zilles
fuente
Tuve una pregunta similar. Y esta es la respuesta correcta para mí.
Calvin Cheng
Ambos tienen una ligera sobrecarga de rendimiento si los elementos en la expresión de matriz no son tipos simples.
nulo
Eso es cierto ... está haciendo una comparación de profundidad total de los objetos componentes. Que puede o no ser lo que quieres. Vea la respuesta aprobada actualmente para una versión que usa angular moderno que no hace una comparación de profundidad completa.
Karen Zilles
Por alguna razón, cuando intentaba usar $ watchCollection sobre una matriz, recibí este error, ¡ TypeError: Object #<Object> has no method '$watchCollection'pero esta solución me ayuda a resolver mi problema!
abottoni
Genial, incluso puedes ver valores dentro de los objetos:$scope.$watch('[chaptercontent.Id, anchorid]', function (newValues, oldValues) { ... }, true);
Serge van den Oever
15

Puede usar funciones en $ watchGroup para seleccionar campos de un objeto en alcance.

        $scope.$watchGroup(
        [function () { return _this.$scope.ViewModel.Monitor1Scale; },   
         function () { return _this.$scope.ViewModel.Monitor2Scale; }],  
         function (newVal, oldVal, scope) 
         {
             if (newVal != oldVal) {
                 _this.updateMonitorScales();
             }
         });
Yang Zhang
fuente
1
¿Por qué lo usas _this? ¿No se lleva el alcance a las funciones sin definirlo explícitamente?
sg.cc
1
Yo uso el mecanografiado, el código presentado es el resultado compilado.
Yang Zhang
12

¿Por qué no simplemente envolverlo en un forEach?

angular.forEach(['a', 'b', 'c'], function (key) {
  scope.$watch(key, function (v) {
    changed();
  });
});

Se trata de la misma sobrecarga que proporcionar una función para el valor combinado, sin tener que preocuparse por la composición del valor .

Erik Aigner
fuente
En este caso log () se ejecutará varias veces, ¿verdad? Creo que en el caso de las funciones anidadas de $ watch no lo haría. Y no debería estar en la solución para ver varios atributos a la vez.
John Doe
No, el registro solo se ejecuta una vez por cambio por propiedad observada. ¿Por qué se invocaría varias veces?
Erik Aigner
Bien, quiero decir que no funcionaría bien si queremos verlos a todos simultáneamente.
John Doe
1
¿Seguro Por qué no? Lo hago así en un proyecto mío. changed()se invoca cada vez que algo cambia en cualquiera de las propiedades. Ese es exactamente el mismo comportamiento que si se proporcionara una función para el valor combinado.
Erik Aigner
1
Es ligeramente diferente en el sentido de que si varios de los elementos de la matriz cambian al mismo tiempo, $ watch solo activará el controlador una vez en lugar de una vez por elemento cambiado.
bingles
11

Una solución un poco más segura para combinar valores podría ser usar lo siguiente como su $watchfunción:

function() { return angular.toJson([item1, item2]) }

o

$scope.$watch(
  function() {
    return angular.toJson([item1, item2]);
  },
  function() {
    // Stuff to do after either value changes
  });
Guyk
fuente
5

El primer parámetro de $ watch puede ser una función o expresión angular . Consulte la documentación sobre $ scope. $ Watch . Contiene mucha información útil sobre cómo funciona el método $ watch: cuándo se llama a watchExpression, cómo angular compara resultados, etc.

Artem Andreev
fuente
4

qué tal si:

scope.$watch(function() { 
   return { 
      a: thing-one, 
      b: thing-two, 
      c: red-fish, 
      d: blue-fish 
   }; 
}, listener...);
wacamo
fuente
2
Sin el tercer trueparámetro a to scope.$watch(como en su ejemplo), listener()se dispararía cada vez, porque el hash anónimo tiene una referencia diferente cada vez.
Peter V. Mørch
Encontré que esta solución funcionó para mi situación. Para mí, era más como: return { a: functionA(), b: functionB(), ... }. Luego verifico los objetos devueltos de los valores nuevos y antiguos entre sí.
RevNoah
4
$scope.$watch('age + name', function () {
  //called when name or age changed
});

Aquí se llamará a la función cuando cambien tanto el valor de edad como el de nombre.

Akash Shinde
fuente
2
También asegúrese de que en este caso no use los argumentos newValue y oldValue que angular pasa a la devolución de llamada porque serán la concatenación resultante y no solo el campo que fue cambiado
Akash Shinde
1

Angular se introdujo $watchGroupen la versión 1.3 con la que podemos ver múltiples variables, con un solo $watchGroupbloque $watchGrouptoma la matriz como primer parámetro en el que podemos incluir todas nuestras variables para ver.

$scope.$watchGroup(['var1','var2'],function(newVals,oldVals){
   console.log("new value of var1 = " newVals[0]);
   console.log("new value of var2 = " newVals[1]);
   console.log("old value of var1 = " oldVals[0]);
   console.log("old value of var2 = " oldVals[1]);
});
Vinit Solanki
fuente