it-swarm-es.com

¿Cómo funciona el enlace de datos en AngularJS?

¿Cómo funciona el enlace de datos en el marco AngularJS?

No he encontrado detalles técnicos en su sitio . Es más o menos claro cómo funciona cuando los datos se propagan de la vista al modelo. Pero, ¿cómo realiza AngularJS el seguimiento de los cambios de las propiedades del modelo sin configuradores y captadores?

Encontré que hay observadores de JavaScript que pueden hacer este trabajo. Pero no se admiten en Internet Explorer 6 y Internet Explorer 7 . Entonces, ¿cómo sabe AngularJS que cambié, por ejemplo, lo siguiente y reflejé este cambio en una vista?

myobject.myproperty="new value";
1878
Pashec

AngularJS recuerda el valor y lo compara con un valor anterior. Esto es una comprobación de suciedad básica. Si hay un cambio en el valor, entonces se dispara el evento de cambio.

El método $apply(), que es lo que llamas cuando estás haciendo la transición de un mundo no AngularJS a un mundo AngularJS, llama $digest(). Un compendio es simplemente viejo control sucio. Funciona en todos los navegadores y es totalmente predecible.

Para contrastar la comprobación de suciedad (AngularJS) frente a los oyentes de cambio ( KnockoutJS y Backbone.js ): Aunque la comprobación de suciedad puede parecer simple e incluso ineficiente (lo abordaré más adelante), resulta que es semánticamente correcto todo el tiempo, mientras que los oyentes del cambio tienen muchos casos extraños en las esquinas y necesitan cosas como el seguimiento de dependencias para hacerlo más correcto semánticamente. El seguimiento de dependencias de KnockoutJS es una característica inteligente para un problema que AngularJS no tiene.

Problemas con los oyentes del cambio:

  • La sintaxis es atroz, ya que los navegadores no lo admiten de forma nativa. Sí, hay proxies, pero no son semánticamente correctos en todos los casos y, por supuesto, no hay proxies en los navegadores antiguos. La conclusión es que la verificación de errores le permite hacer POJO , mientras que KnockoutJS y Backbone.js lo obligan a heredar de sus clases y acceder a sus datos a través de los accesores.
  • Cambiar la coalescencia. Supongamos que tienes una serie de elementos. Digamos que desea agregar elementos a una matriz, ya que está haciendo un bucle para agregar, cada vez que agregue está activando eventos en el cambio, lo que representa la interfaz de usuario. Esto es muy malo para el rendimiento. Lo que quieres es actualizar la interfaz de usuario solo una vez, al final. Los eventos de cambio son demasiado finos.
  • Los oyentes de cambio se activan inmediatamente en un setter, lo cual es un problema, ya que el oyente de cambios puede cambiar aún más los datos, lo que genera más eventos de cambio. Esto es malo ya que en tu pila puedes tener varios eventos de cambio sucediendo a la vez. Supongamos que tiene dos matrices que deben mantenerse sincronizadas por cualquier motivo. Solo puedes agregar a uno u otro, pero cada vez que agregas, disparas un evento de cambio, que ahora tiene una visión inconsistente del mundo. Este es un problema muy similar al bloqueo de subprocesos, que JavaScript evita ya que cada devolución de llamada se ejecuta exclusivamente y hasta su finalización. Los eventos de cambio rompen esto ya que los establecedores pueden tener consecuencias de gran alcance que no son intencionadas y no son obvias, lo que crea el problema del hilo nuevamente. Resulta que lo que usted quiere hacer es retrasar la ejecución del oyente y garantizar que solo un oyente se ejecuta a la vez, por lo tanto, cualquier código puede cambiar los datos, y sabe que ningún otro código se ejecuta mientras lo hace. .

¿Qué pasa con el rendimiento?

Por lo tanto, puede parecer que somos lentos, ya que la comprobación de errores es ineficaz. Aquí es donde debemos ver los números reales en lugar de tener argumentos teóricos, pero primero definamos algunas restricciones.

Los humanos son:

  • Lento - Cualquier cosa más rápida que 50 ms es imperceptible para los humanos y, por lo tanto, puede considerarse como "instantánea".

  • Limitado - Realmente no puede mostrar más de 2000 piezas de información a un humano en una sola página. Cualquier cosa más que eso es una IU realmente mala, y los humanos no pueden procesar esto de todos modos.

Entonces la pregunta real es esta: ¿Cuántas comparaciones puedes hacer en un navegador en 50 ms? Esta es una pregunta difícil de responder ya que muchos factores entran en juego, pero aquí hay un caso de prueba: http://jsperf.com/angularjs-digest/6 que crea 10,000 observadores. En un navegador moderno, esto toma poco menos de 6 ms. En Internet Explorer 8 tarda unos 40 ms. Como puede ver, esto no es un problema, incluso en navegadores lentos en estos días. Hay una advertencia: las comparaciones deben ser simples para ajustarse al límite de tiempo ... Desafortunadamente, es demasiado fácil agregar una comparación lenta en AngularJS, por lo que es fácil crear aplicaciones lentas cuando no sabes lo que haces. estás haciendo. Pero esperamos tener una respuesta al proporcionar un módulo de instrumentación, que le mostrará cuáles son las comparaciones lentas.

Resulta que los videojuegos y las GPU utilizan el método de comprobación de errores, específicamente porque es consistente. Siempre que superen la frecuencia de actualización del monitor (por lo general, 50-60 Hz, o cada 16.6-20 ms), cualquier rendimiento sobre eso es un desperdicio, por lo que es mejor dibujar más cosas que obtener FPS más alto.

2711
Misko Hevery

Misko ya dio una excelente descripción de cómo funcionan los enlaces de datos, pero me gustaría agregar mi opinión sobre el problema de rendimiento con el enlace de datos.

Como dijo Misko, alrededor de 2000 enlaces son donde empiezas a ver problemas, pero de todos modos no debes tener más de 2000 datos en una página. Esto puede ser cierto, pero no todos los enlaces de datos son visibles para el usuario. Una vez que comience a construir cualquier tipo de widget o cuadrícula de datos con enlace bidireccional, puede fácilmente golpear 2000 enlaces, sin tener una mala experiencia de usuario.

Considere, por ejemplo, un cuadro combinado donde puede escribir texto para filtrar las opciones disponibles. Este tipo de control podría tener ~ 150 elementos y seguir siendo altamente utilizable. Si tiene alguna característica adicional (por ejemplo, una clase específica en la opción seleccionada actualmente), comienza a obtener de 3 a 5 enlaces por opción. Coloque tres de estos widgets en una página (por ejemplo, uno para seleccionar un país, el otro para seleccionar una ciudad en dicho país y el tercero para seleccionar un hotel) y ya está entre 1000 y 2000 enlaces.

O considere una cuadrícula de datos en una aplicación web corporativa. 50 filas por página no son irrazonables, cada una de las cuales podría tener entre 10 y 20 columnas. Si creas esto con ng-repeticiones, y/o tienes información en algunas celdas que usa algunos enlaces, podrías acercarte a 2000 enlaces solo con esta cuadrícula.

Me parece que este es un problema enorme cuando se trabaja con AngularJS, y la única solución que he podido encontrar hasta ahora es crear widgets sin usar enlaces de doble vía, en lugar de usar ngOnce, desregistrar observadores y trucos similares, o construyen directivas que construyen el DOM con jQuery y la manipulación DOM. Siento que esto derrota el propósito de usar Angular en primer lugar.

Me encantaría escuchar sugerencias sobre otras formas de manejar esto, pero tal vez debería escribir mi propia pregunta. Quería poner esto en un comentario, pero resultó ser demasiado largo para eso ...

TL; DR
El enlace de datos puede causar problemas de rendimiento en páginas complejas.

315
MW.

Comprobando el objeto $scope sucio

Angular mantiene una simple array de observadores en los objetos $scope. Si inspecciona cualquier $scope encontrará que contiene una array llamada $$watchers.

Cada observador es una object que contiene entre otras cosas

  1. Una expresión que el observador está monitoreando. Esto podría ser simplemente un nombre attribute, o algo más complicado.
  2. Un último valor conocido de la expresión. Esto se puede comparar con el valor calculado actual de la expresión. Si los valores difieren, el observador activará la función y marcará el $scope como sucio.
  3. Una función que se ejecutará si el observador está sucio.

Cómo se definen los observadores

Hay muchas maneras diferentes de definir un observador en AngularJS.

  • Puede explícitamente $watch an attribute en $scope.

    $scope.$watch('person.username', validateUnique);
    
  • Puede colocar una interpolación {{}} en su plantilla (se creará un observador para usted en el $scope actual).

    <p>username: {{person.username}}</p>
    
  • Puede solicitar una directiva como ng-model para definir el observador por usted.

    <input ng-model="person.username" />
    

El ciclo $digest verifica a todos los observadores contra su último valor

Cuando interactuamos con AngularJS a través de los canales normales (ng-model, ng-repeat, etc.), la directiva activará un ciclo de resumen.

Un ciclo de compendio es un cruce de profundidad primero de $scope y todos sus hijos . Para cada $scopeobject, iteramos sobre su $$watchersarray y evaluamos todas las expresiones. Si el nuevo valor de expresión es diferente del último valor conocido, se llama a la función del observador. Esta función puede recompilar parte del DOM, volver a calcular un valor en $scope, desencadenar una AJAXrequest, cualquier cosa que necesite hacer.

Cada ámbito se recorre y cada expresión de observación se evalúa y se compara con el último valor.

Si se activa un observador, el $scope está sucio

Si se activa un observador, la aplicación sabe que algo ha cambiado y el $scope está marcado como sucio.

Las funciones del observador pueden cambiar otros atributos en $scope o en un $scope padre. Si se ha activado una función $watcher, no podemos garantizar que nuestros otros $scopes todavía estén limpios, por lo que ejecutamos todo el ciclo de resumen nuevamente.

Esto se debe a que AngularJS tiene enlace bidireccional, por lo que los datos pueden pasarse de nuevo por el árbol $scope. Podemos cambiar un valor en un $scope superior que ya ha sido digerido. Quizás cambiemos un valor en el $rootScope.

Si el $digest está sucio, ejecutamos todo el ciclo $digest nuevamente

Continuamos recorriendo el ciclo $digest hasta que el ciclo de resumen salga limpio (todas las expresiones $watch tienen el mismo valor que tenían en el ciclo anterior), o alcanzamos el límite de resumen. De forma predeterminada, este límite se establece en 10.

Si alcanzamos el límite de resumen, AngularJS generará un error en la consola:

10 $digest() iterations reached. Aborting!

El compendio es duro para la máquina pero fácil para el desarrollador

Como puede ver, cada vez que algo cambia en una aplicación AngularJS, AngularJS verificará a cada observador en la jerarquía $scope para ver cómo responder. Para un desarrollador, esto es un beneficio masivo de productividad, ya que ahora necesita escribir casi sin código de cableado, AngularJS solo notará si un valor ha cambiado y hará que el resto de la aplicación sea consistente con el cambio.

Desde la perspectiva de la máquina, esto es muy ineficiente y ralentizará nuestra aplicación si creamos demasiados observadores. Misko ha citado una cifra de alrededor de 4000 observadores antes de que su aplicación se sienta lenta en los navegadores más antiguos.

Es fácil llegar a este límite si ng-repeat sobre una gran JSONarray por ejemplo. Puede mitigar esto utilizando características como el enlace de una sola vez para compilar una plantilla sin crear observadores.

Cómo evitar crear demasiados observadores.

Cada vez que su usuario interactúa con su aplicación, cada observador en su aplicación será evaluado al menos una vez. Una gran parte de la optimización de una aplicación AngularJS es reducir el número de observadores en su árbol $scope. Una forma fácil de hacer esto es con un enlace de tiempo .

Si tiene datos que raramente cambiarán, puede enlazarlos solo una vez utilizando la :: sintaxis, así:

<p>{{::person.username}}</p>

o

<p ng-bind="::person.username"></p>

El enlace solo se activará cuando la plantilla que contiene se represente y los datos se carguen en $scope.

Esto es especialmente importante cuando tiene un ng-repeat con muchos elementos.

<div ng-repeat="person in people track by username">
  {{::person.username}}
</div>
151
superluminary

Este es mi entendimiento básico. Bien puede estar equivocado!

  1. Los elementos se observan pasando una función (devolviendo lo que se va a ver) al método $watch.
  2. Los cambios a los elementos observados deben realizarse dentro de un bloque de código envuelto por el método $apply.
  3. Al final de $apply, se invoca el método $digest que pasa por cada uno de los relojes y verifica si han cambiado desde la última vez que se ejecutó $digest.
  4. Si se encuentran cambios, se invoca de nuevo el compendio hasta que todos los cambios se estabilicen.

En el desarrollo normal, la sintaxis de enlace de datos en el HTML le dice al compilador AngularJS que cree los relojes para usted y que los métodos del controlador ya se ejecutan dentro de $apply. Así que para el desarrollador de aplicaciones todo es transparente.

79
Pete BD

Me pregunté esto por un tiempo. Sin los configuradores, ¿cómo advierte AngularJS los cambios en el objeto $scope? ¿Los encuestan?

Lo que realmente hace es esto: cualquier lugar "normal" en el que modifique el modelo ya fue llamado desde las entrañas de AngularJS, por lo que automáticamente llama $apply después de que se ejecuta el código. Digamos que su controlador tiene un método que está conectado a ng-click en algún elemento. Debido a que AngularJS une la llamada de ese método para usted, tiene la posibilidad de hacer un $apply en el lugar apropiado. Del mismo modo, para las expresiones que aparecen justo en las vistas, éstas se ejecutan mediante AngularJS, por lo que hace $apply.

Cuando la documentación habla de tener que llamar $apply manualmente para el código fuera de AngularJS, se trata de un código que, cuando se ejecuta, no proviene de AngularJS en la pila de llamadas.

61
jpsimons

Explicando con fotos:

Enlace de datos necesita un mapeo

La referencia en el alcance no es exactamente la referencia en la plantilla. Cuando vincula datos a dos objetos, necesita un tercero que escuche el primero y modifique el otro.

 enter image description here

Aquí, cuando modifica el <input>, toca el data-ref3 . Y el mecanismo clásico de enlace de datos cambiará data-ref4 . Entonces, ¿cómo se moverán las otras expresiones de {{data}}?

Los eventos llevan a $ digest ()

 enter image description here

Angular mantiene una oldValue y newValue de cada enlace. Y después de cada Angular evento, el famoso bucle $digest() verificará la Lista de vigilancia para ver si algo ha cambiado. Estos Angular eventos son ng-click, ng-change, $http completado ... La $digest() se repetirá siempre que cualquier oldValue difiera de la newValue.

En la imagen anterior, notará que los datos-ref1 y data-ref2 han cambiado.

Conclusiones

Es un poco como el huevo y el pollo. Nunca se sabe quién comienza, pero espero que funcione la mayor parte del tiempo como se esperaba.

El otro punto es que puede comprender fácilmente el impacto profundo de un enlace simple en la memoria y la CPU. Esperemos que los escritorios sean lo suficientemente gordos para manejar esto. Los teléfonos móviles no son tan fuertes.

32
Nicolas Zozol

Obviamente, no hay una comprobación periódica de Scope si hay algún cambio en los Objetos adjuntos. No se observan todos los objetos adjuntos al alcance. El alcance prototípicamente mantiene un $$ observadores . Scope solo itera a través de este $$watchers cuando se llama $digest.

Angular agrega un observador a los observadores de $$ para cada uno de estos

  1. {{expresión}} - En sus plantillas (y en cualquier otro lugar donde haya una expresión) o cuando definamos ng-model.
  2. $ scope. $ watch (‘expresión/función’) - En su JavaScript, simplemente podemos adjuntar un objeto de alcance para que lo vea angular.

$ watch function toma en tres parámetros:

  1. La primera es una función de observador que simplemente devuelve el objeto o simplemente podemos agregar una expresión.

  2. La segunda es una función de escucha que se llamará cuando haya un cambio en el objeto. Todas las cosas como los cambios de DOM se implementarán en esta función.

  3. El tercero es un parámetro opcional que toma un booleano. Si es verdadero, el profundo angular observa el objeto y si es falso Angular simplemente hace una referencia al objeto. La implementación aproximada de $ watch se ve así

Scope.prototype.$watch = function(watchFn, listenerFn) {
   var watcher = {
       watchFn: watchFn,
       listenerFn: listenerFn || function() { },
       last: initWatchVal  // initWatchVal is typically undefined
   };
   this.$$watchers.Push(watcher); // pushing the Watcher Object to Watchers  
};

Hay una cosa interesante en Angular llamada Digest Cycle. El ciclo de $ digest comienza como resultado de una llamada a $ scope. $ Digest (). Supongamos que cambia un modelo de $ scope en una función de controlador a través de la directiva ng-click. En ese caso, AngularJS activa automáticamente un ciclo de $ digest llamando a $ digest (). Además de ng-click, hay otras directivas/servicios incorporados que le permiten cambiar modelos (por ejemplo, ng-model, $ timeout, etc.) y desencadenar automáticamente un ciclo $ digest. La implementación aproximada de $ digest se ve así.

Scope.prototype.$digest = function() {
      var dirty;
      do {
          dirty = this.$$digestOnce();
      } while (dirty);
}
Scope.prototype.$$digestOnce = function() {
   var self = this;
   var newValue, oldValue, dirty;
   _.forEach(this.$$watchers, function(watcher) {
          newValue = watcher.watchFn(self);
          oldValue = watcher.last;   // It just remembers the last value for dirty checking
          if (newValue !== oldValue) { //Dirty checking of References 
   // For Deep checking the object , code of Value     
   // based checking of Object should be implemented here
             watcher.last = newValue;
             watcher.listenerFn(newValue,
                  (oldValue === initWatchVal ? newValue : oldValue),
                   self);
          dirty = true;
          }
     });
   return dirty;
 };

Si usamos la función setTimeout () de JavaScript para actualizar un modelo de alcance, Angular no tiene forma de saber qué puede cambiar. En este caso, es nuestra responsabilidad llamar a $ apply () manualmente, lo que desencadena un ciclo de $ digest. De manera similar, si tiene una directiva que configura un detector de eventos DOM y cambia algunos modelos dentro de la función del controlador, debe llamar a $ apply () para garantizar que los cambios surtan efecto. La gran idea de $ apply es que podemos ejecutar algunos códigos que no son conscientes de Angular, que pueden cambiar las cosas en el alcance. Si envolvemos ese código en $ apply, se ocupará de llamar a $ digest (). Implementación aproximada de $ apply ().

Scope.prototype.$apply = function(expr) {
       try {
         return this.$eval(expr); //Evaluating code in the context of Scope
       } finally {
         this.$digest();
       }
};
21
Sasank Sunkavalli

AngularJS maneja el mecanismo de enlace de datos con la ayuda de tres funciones poderosas: $ watch () , $ digest () y $ apply () . La mayoría de las veces, AngularJS llamará a $ scope. $ Watch () y $ scope. $ Digest (), pero en algunos casos es posible que tenga que llamar estas funciones manualmente para actualizar con nuevos valores.

$ watch () : -

Esta función se utiliza para observar los cambios en una variable en el $ scope. Acepta tres parámetros: expresión, objeto de escucha e igualdad, donde el objeto de escucha y la igualdad son parámetros opcionales.

$ digest () -

Esta función se repite a través de todos los relojes en el objeto $ scope y sus objetos secundarios $ scope
(si tiene alguno). Cuando $ digest () itera sobre los relojes, comprueba si el valor de la expresión ha cambiado. Si el valor ha cambiado, AngularJS llama al oyente con un nuevo valor y un valor antiguo. La función $ digest () se llama siempre que AngularJS lo considere necesario. Por ejemplo, después de hacer clic en un botón, o después de una llamada AJAX. Puede tener algunos casos en los que AngularJS no llama la función $ digest () por usted. En ese caso tienes que llamarlo tú mismo.

$ apply () -

Angular do auto-mágicamente actualiza solo aquellos cambios de modelo que están dentro del contexto de AngularJS. Cuando cambie en cualquier modelo fuera del contexto Angular (como eventos DOM del navegador, setTimeout, XHR o bibliotecas de terceros), deberá informar a Angular de los cambios llamando al $ aplicar () manualmente. Cuando la llamada a la función $ apply () finaliza, AngularJS llama a $ digest () internamente, por lo que todos los enlaces de datos se actualizan.

14
Bharath Kumar

Sucedió que necesitaba vincular un modelo de datos de una persona con un formulario, lo que hice fue un mapeo directo de los datos con el formulario.

Por ejemplo si el modelo tuviera algo como:

$scope.model.people.name

El control de entrada del formulario:

<input type="text" name="namePeople" model="model.people.name">

De esa manera, si modifica el valor del controlador de objetos, esto se reflejará automáticamente en la vista.

Un ejemplo en el que pasé el modelo que se actualiza a partir de los datos del servidor es cuando solicita un código postal y un código postal basado en cargas escritas, una lista de colonias y ciudades asociadas con esa vista y, de manera predeterminada, establece el primer valor con el usuario. Y esto funcionó muy bien, lo que sucede, es que angularJS a veces toma unos segundos para actualizar el modelo, para hacer esto puedes poner una flecha giratoria mientras se muestran los datos.

6
gartox
  1. El enlace de datos unidireccional es un enfoque en el que se toma un valor del modelo de datos y se inserta en un elemento HTML. No hay manera de actualizar el modelo desde la vista. Se utiliza en sistemas de plantillas clásicas. Estos sistemas enlazan datos en una sola dirección.

  2. El enlace de datos en Angular aplicaciones es la sincronización automática de datos entre el modelo y los componentes de la vista.

El enlace de datos le permite tratar el modelo como la única fuente de verdad en su aplicación. La vista es una proyección del modelo en todo momento. Si se cambia el modelo, la vista refleja el cambio y viceversa.

5
Shankar Gangadhar

AngularJs admite Enlace de datos bidireccional .
Significa que puede acceder a los datos Ver -> Controlador y Controlador -> Ver

Por ej.

1)

// If $scope have some value in Controller. 
$scope.name = "Peter";

// HTML
<div> {{ name }} </div>

O/P

Peter

Puede enlazar datos en ng-model Like: -
2)

<input ng-model="name" />

<div> {{ name }} </div>

Aquí, en el ejemplo anterior, cualquier entrada que proporcione el usuario, será visible en la etiqueta <div>.

Si desea vincular la entrada de html al controlador: -
3)

<form name="myForm" ng-submit="registration()">
   <label> Name </lbel>
   <input ng-model="name" />
</form>

Aquí si quiere usar la entrada name en el controlador, entonces,

$scope.name = {};

$scope.registration = function() {
   console.log("You will get the name here ", $scope.name);
};

ng-model une nuestra vista y la representa en la expresión {{ }}.
ng-model son los datos que se muestran al usuario en la vista y con los que el usuario interactúa.
Por lo tanto, es fácil vincular datos en AngularJs.

4
ojus kulkarni

Aquí hay un ejemplo de enlace de datos con AngularJS, usando un campo de entrada. Te explico mas tarde

Código HTML

<div ng-app="myApp" ng-controller="myCtrl" class="formInput">
     <input type="text" ng-model="watchInput" Placeholder="type something"/>
     <p>{{watchInput}}</p> 
</div>

Código AngularJS

myApp = angular.module ("myApp", []);
myApp.controller("myCtrl", ["$scope", function($scope){
  //Your Controller code goes here
}]);

Como puede ver en el ejemplo anterior, AngularJS usa ng-model para escuchar y observar lo que sucede en los elementos HTML, especialmente en los campos input. Cuando algo sucede, haz algo. En nuestro caso, ng-model está vinculado a nuestra vista, utilizando la notación de bigote {{}}. Todo lo que se escribe dentro del campo de entrada se muestra instantáneamente en la pantalla. Y esa es la belleza del enlace de datos, utilizando AngularJS en su forma más simple.

Espero que esto ayude.

Vea un ejemplo de trabajo aquí en Codepen

4
AllJs

Angular.js crea un observador para cada modelo que creamos a la vista. Cada vez que se cambia un modelo, se agrega una clase "ng-dirty" al modelo, por lo que el observador observará todos los modelos que tengan la clase "ng-dirty" y actualizará sus valores en el controlador y viceversa.

3
Shankar Gangadhar