¿Cómo uso $ scope. $ Watch y $ scope. $ Apply en AngularJS?

1088

No entiendo cómo usar $scope.$watchy $scope.$apply. La documentación oficial no es útil.

Lo que no entiendo específicamente:

  • ¿Están conectados al DOM?
  • ¿Cómo puedo actualizar los cambios de DOM en el modelo?
  • ¿Cuál es el punto de conexión entre ellos?

Intenté este tutorial , pero toma la comprensión de $watchy $applypor sentado.

¿Qué hago $applyy $watchhago, y cómo los uso adecuadamente?

ilyo
fuente

Respuestas:

1737

Debe saber cómo funciona AngularJS para comprenderlo.

Ciclo de resumen y $ scope

En primer lugar, AngularJS define un concepto del llamado ciclo de digestión . Este ciclo puede considerarse como un ciclo, durante el cual AngularJS verifica si hay algún cambio en todas las variables observadas por todos los $scopes. Entonces, si ha $scope.myVardefinido en su controlador y esta variable fue marcada para ser observada , entonces está indicando implícitamente a AngularJS que monitoree los cambios myVaren cada iteración del ciclo.

Una pregunta de seguimiento natural sería: ¿Está todo relacionado con $scopeser observado? Afortunadamente no. Si observa los cambios en cada objeto en su $scope, entonces un ciclo de resumen tardaría años en evaluarse y rápidamente tendría problemas de rendimiento. Es por eso que el equipo de AngularJS nos dio dos formas de declarar algunas $scopevariables como observadas (lea a continuación).

$ watch ayuda a escuchar los cambios de $ alcance

Hay dos formas de declarar una $scopevariable como observada.

  1. Al usarlo en su plantilla a través de la expresión <span>{{myVar}}</span>
  2. Al agregarlo manualmente a través del $watchservicio

Anuncio 1) Este es el escenario más común y estoy seguro de que lo has visto antes, pero no sabías que esto ha creado un reloj en segundo plano. Sí, lo hizo! El uso de directivas AngularJS (como ng-repeat) también puede crear relojes implícitos.

Anuncio 2) Así es como creas tus propios relojes . $watchEl servicio le ayuda a ejecutar algún código cuando algún valor adjunto al $scopeha cambiado. Raramente se usa, pero a veces es útil. Por ejemplo, si desea ejecutar algún código cada vez que cambia 'myVar', puede hacer lo siguiente:

function MyController($scope) {

    $scope.myVar = 1;

    $scope.$watch('myVar', function() {
        alert('hey, myVar has changed!');
    });

    $scope.buttonClicked = function() {
        $scope.myVar = 2; // This will trigger $watch expression to kick in
    };
}

$ apply permite integrar cambios con el ciclo de resumen

Puede pensar en la $applyfunción como un mecanismo de integración . Verá, cada vez que cambia alguna variable observada$scope directamente al objeto, AngularJS sabrá que el cambio ha sucedido. Esto se debe a que AngularJS ya sabía monitorear esos cambios. Entonces, si sucede en el código administrado por el marco, el ciclo de resumen continuará.

Sin embargo, a veces desea cambiar algún valor fuera del mundo AngularJS y ver que los cambios se propagan normalmente. Considere esto: tiene un $scope.myVarvalor que se modificará dentro de un $.ajax()controlador jQuery . Esto sucederá en algún momento en el futuro. AngularJS no puede esperar a que esto suceda, ya que no se le ha ordenado que espere en jQuery.

Para hacer frente a esto, $applyse ha introducido. Le permite comenzar el ciclo de digestión explícitamente. Sin embargo, solo debe usar esto para migrar algunos datos a AngularJS (integración con otros marcos), pero nunca use este método combinado con el código regular de AngularJS, ya que AngularJS arrojará un error en ese momento.

¿Cómo se relaciona todo esto con el DOM?

Bueno, realmente deberías seguir el tutorial nuevamente, ahora que sabes todo esto. El ciclo de resumen asegurará que la IU y el código JavaScript permanezcan sincronizados, evaluando cada observador conectado a todos $scopelos mensajes mientras no cambie nada. Si no se producen más cambios en el ciclo de resumen, se considera que está terminado.

Puede adjuntar objetos al $scopeobjeto explícitamente en el Controlador o declarándolos en {{expression}}forma directamente en la vista.

Espero que eso ayude a aclarar algunos conocimientos básicos sobre todo esto.

Lecturas adicionales:

ŁukaszBachman
fuente
57
"Angular verifica si hay algún cambio en todas las variables asociadas a todos los $ scopes". No creo que sea del todo correcto. Creo que Angular solo (sucio) verifica las propiedades de $ scope que han configurado $ watch (tenga en cuenta que usar {{}} en una vista creará un $ watch automáticamente). Consulte también la sección "Consideraciones de rendimiento de Scope $ watch" en la página de Scope .
Mark Rajcok
55
Ese podría ser el caso. Intentaré encontrar algo de tiempo para leer más al respecto y editar mi respuesta.
ŁukaszBachman
15
@ MarkRajcok, tenías razón. Modifiqué mi respuesta y señalé un artículo que muestra muy bien cómo se implementa.
ŁukaszBachman
3
¿Qué hay de usar esto? (Método "Control as")
Leandro
2
El uso de "Control as" no debería tener ningún impacto en la información anterior. El uso de this.myVar pone myVar en el alcance.
Marcus Rådell
161

En AngularJS, actualizamos nuestros modelos, y nuestras vistas / plantillas actualizan el DOM "automáticamente" (a través de directivas integradas o personalizadas).

$ apply y $ watch, ambos métodos de alcance, no están relacionados con el DOM.

La página Conceptos (sección "Tiempo de ejecución") tiene una explicación bastante buena del bucle $ digest, $ apply, la cola $ evalAsync y la lista $ watch. Aquí está la imagen que acompaña al texto:

$ digest loop

Cualquier código que tenga acceso a un alcance, normalmente controladores y directivas (sus funciones de enlace y / o sus controladores), puede configurar una " watchExpression " que AngularJS evaluará en relación con ese alcance. Esta evaluación ocurre cada vez que AngularJS ingresa a su bucle $ digest (en particular, el bucle "$ watch list"). Puede ver propiedades de ámbito individuales, puede definir una función para ver dos propiedades juntas, puede ver la longitud de una matriz, etc.

Cuando las cosas suceden "dentro de AngularJS", por ejemplo, escribe en un cuadro de texto que tiene habilitado el enlace de datos bidireccional de AngularJS (es decir, usa ng-model), un $ http callback dispara, etc. - $ apply ya se ha llamado, por lo que estás dentro del rectángulo "AngularJS" en la figura de arriba. Se evaluarán todas las watchExpressions (posiblemente más de una vez, hasta que no se detecten más cambios).

Cuando las cosas suceden "fuera de AngularJS", por ejemplo, usaste bind () en una directiva y luego ese evento se dispara, lo que resulta en que se llame tu devolución de llamada, o algunos incendios de devolución de llamada registrados por jQuery, todavía estamos en el rectángulo "Nativo". Si el código de devolución de llamada modifica cualquier cosa que $ watch está viendo, llame a $ apply para ingresar al rectángulo AngularJS, haciendo que se ejecute el bucle $ digest y, por lo tanto, AngularJS notará el cambio y hará su magia.

Mark Rajcok
fuente
55
Entiendo la idea, lo que no entiendo es cómo se transfieren los datos. Tengo un modelo que es un objeto con muchos datos, uso algunos para manipular el DOM. entonces algo de eso se cambia. ¿Cómo coloco los datos modificados en el lugar correcto en el modelo? En el ejemplo que utilicé, él hace la manipulación y al final simplemente usa scope.$apply(scope.model), no entiendo qué datos se transfieren y cómo se transfieren al lugar correcto en el modelo.
ilyo
66
No hay transferencia de datos mágica que tenga lugar. Normalmente con las aplicaciones angulares, debe cambiar los modelos angulares, que luego impulsan las actualizaciones de vista / DOM. Si actualiza el DOM fuera de Angular, deberá actualizar manualmente los modelos. scope.$apply(scope.model)simplemente se evaluará scope.modelcomo una expresión angular y luego ingresará un bucle $ digest. En el artículo al que hace referencia, probablemente scope.$apply()sería suficiente, ya que el modelo ya está siendo $ vigilado. La función stop () está actualizando el modelo (creo que toUpdate es una referencia a scope.model), y luego se llama a $ apply.
Mark Rajcok
Parece que los documentos de AngularJS se han desplazado por debajo de esta respuesta (el primer enlace no tiene "tiempo de ejecución" o $watchen la página, y el segundo enlace está roto, a partir de ahora, de todos modos). Dolorosamente, las versiones de archivo no almacenaron en caché el proceso asíncrono que creó el contenido.
Ruffin
52

AngularJS extiende este bucle de eventos , creando algo llamado AngularJS context.

$ watch ()

Cada vez que vincula algo en la interfaz de usuario, inserta un $watchen una $watchlista .

User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />

Aquí tenemos $scope.user, que está vinculado a la primera entrada, y tenemos $scope.pass, que está vinculado a la segunda. Al hacer esto, agregamos dos $watches a la $watchlista .

Cuando se carga nuestra plantilla , AKA en la fase de vinculación, el compilador buscará todas las directivas y creará todos los $watches necesarios.

AngularJS proporciona $watch, $watchcollectiony $watch(true). A continuación se muestra un diagrama ordenado que explica en profundidad los tres tomados de los observadores .

Ingrese la descripción de la imagen aquí

angular.module('MY_APP', []).controller('MyCtrl', MyCtrl)
function MyCtrl($scope,$timeout) {
  $scope.users = [{"name": "vinoth"},{"name":"yusuf"},{"name":"rajini"}];

  $scope.$watch("users", function() {
    console.log("**** reference checkers $watch ****")
  });

  $scope.$watchCollection("users", function() {
    console.log("**** Collection  checkers $watchCollection ****")
  });

  $scope.$watch("users", function() {
    console.log("**** equality checkers with $watch(true) ****")
  }, true);

  $timeout(function(){
     console.log("Triggers All ")
     $scope.users = [];
     $scope.$digest();

     console.log("Triggers $watchCollection and $watch(true)")
     $scope.users.push({ name: 'Thalaivar'});
     $scope.$digest();

     console.log("Triggers $watch(true)")
     $scope.users[0].name = 'Superstar';
     $scope.$digest();
  });
}

http://jsfiddle.net/2Lyn0Lkb/

$digest lazo

Cuando el navegador recibe un evento que puede ser manejado por el contexto AngularJS $digest, se activará el bucle. Este bucle está hecho de dos bucles más pequeños. Uno procesa la $evalAsynccola y el otro procesa el $watch list. El $digestrecorrerá la lista de lo $watchque tenemos

app.controller('MainCtrl', function() {
  $scope.name = "vinoth";

  $scope.changeFoo = function() {
      $scope.name = "Thalaivar";
  }
});

{{ name }}
<button ng-click="changeFoo()">Change the name</button>

Aquí solo tenemos uno $watchporque ng-click no crea ningún reloj.

Presionamos el botón.

  1. El navegador recibe un evento que entrará en el contexto AngularJS
  2. El $digestbucle se ejecutará y solicitará cambios a cada $ watch.
  3. Dado $watchque el que estaba buscando cambios en $ scope.name informa un cambio, forzará otro $digestciclo.
  4. El nuevo bucle no informa nada.
  5. El navegador recupera el control y actualizará el DOM reflejando el nuevo valor de $ scope.name
  6. Lo importante aquí es que CADA evento que ingrese al contexto AngularJS ejecutará un $digestciclo. Eso significa que cada vez que escribimos una carta en una entrada, el bucle se ejecutará comprobando $watchen cada página.

$ apply ()

Si llama $applycuando se dispara un evento, pasará por el contexto angular, pero si no lo llama, se ejecutará fuera de él. Es tan fácil como eso. $applyllamará al$digest() bucle internamente e iterará sobre todos los relojes para garantizar que el DOM se actualice con el valor recién actualizado.

El $apply()método activará observadores en toda la $scopecadena, mientras que el $digest()método solo activará observadores en la corriente $scopey su corriente children. Cuando ninguno de los $scopeobjetos superiores necesita saber acerca de los cambios locales, puede usar $digest().

Thalaivar
fuente
18

Me pareció muy vídeos en profundidad que cubren $watch, $apply, $digesty digerir los ciclos de:

A continuación hay un par de diapositivas utilizadas en esos videos para explicar los conceptos (por si acaso, si los enlaces anteriores se eliminan / no funcionan).

Ingrese la descripción de la imagen aquí

En la imagen de arriba, "$ scope.c" no se está viendo ya que no se usa en ninguno de los enlaces de datos (en el marcado). Los otros dos ( $scope.ay $scope.b) serán observados.

Ingrese la descripción de la imagen aquí

De la imagen de arriba: Basado en el evento del navegador respectivo, AngularJS captura el evento, realiza un ciclo de resumen (revisa todos los relojes para detectar cambios), ejecuta funciones de reloj y actualiza el DOM. Si no eventos del navegador, el ciclo de digestión se pueden accionar manualmente usando $applyo $digest.

Más sobre $applyy $digest:

Ingrese la descripción de la imagen aquí

usuario203687
fuente
17

Hay $watchGroupy $watchCollectiontambién. Específicamente, $watchGroupes realmente útil si desea llamar a una función para actualizar un objeto que tiene múltiples propiedades en una vista que no es un objeto dom, por ejemplo, otra vista en lienzo, WebGL o solicitud del servidor.

Aquí, el enlace de documentación .

Utkarsh Bhardwaj
fuente
Hubiera comentado sobre el $watchCollectionpero veo que ya lo hiciste. Aquí hay documentación al respecto del sitio AngularJS. Proporcionan una muy buena visual de la $watchprofundidad. Tenga en cuenta que la información está cerca de la parte inferior de la página.
JabberwockyDecompiler
15

Simplemente termine de leer TODO lo anterior, aburrido y con sueño (lo siento, pero es cierto). Muy técnico, profundo, detallado y seco. ¿Por qué estoy escribiendo? Debido a que AngularJS es masivo, muchos conceptos interconectados pueden volver loco a cualquiera. A menudo me preguntaba, ¿no soy lo suficientemente inteligente como para entenderlos? ¡No! ¡Es porque muy pocos pueden explicar la tecnología en un lenguaje para tontos sin todas las terminologías! Ok, déjame intentarlo:

1) Todas son cosas impulsadas por eventos. (Escucho la risa, pero sigue leyendo)

Si no sabe qué es el evento, entonces piense que coloca un botón en la página, conéctelo con una función usando "on-click", esperando que los usuarios hagan clic en él para activar las acciones que planta dentro del función. O piense en el "disparador" de SQL Server / Oracle.

2) $ watch es "al hacer clic".

Lo especial es que toma 2 funciones como parámetros, la primera da el valor del evento, la segunda toma el valor en consideración ...

3) $ digest es el jefe que revisa incansablemente , bla-bla-bla pero un buen jefe.

4) $ apply le brinda el camino cuando desea hacerlo manualmente , como a prueba de fallas (en caso de que el clic no se active, lo obliga a ejecutarse).

Ahora, hagámoslo visual. Imagine esto para que sea aún más fácil captar la idea:

En un restaurante,

- camareros

se supone que deben tomar pedidos de clientes, esto es

$watch(
  function(){return orders;},
  function(){Kitchen make it;}
);

- GERENTE corriendo para asegurarse de que todos los camareros estén despiertos, atentos a cualquier signo de cambios de los clientes. Esto es$digest()

- EL PROPIETARIO tiene el máximo poder para conducir a todos a pedido, esto es$apply()

Jeb50
fuente
2
Esto puede ser entendido por un niño de 5 años. Agradezco este tipo de respuesta. +1
Chris22