¿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";
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.
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.
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.
$scope
sucioAngular 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
attribute
, o algo más complicado.$scope
como sucio.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" />
$digest
verifica a todos los observadores contra su último valorCuando 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 $scope
object
, iteramos sobre su $$watchers
array
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 AJAX
request
, 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.
$scope
está sucioSi 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 $scope
s 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
.
$digest
está sucio, ejecutamos todo el ciclo $digest
nuevamenteContinuamos 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!
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 JSON
array
por ejemplo. Puede mitigar esto utilizando características como el enlace de una sola vez para compilar una plantilla sin crear 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>
Este es mi entendimiento básico. Bien puede estar equivocado!
$watch
.$apply
.$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
.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.
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.
Explicando con fotos:
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.
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}}
?
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.
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.
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
- {{expresión}} - En sus plantillas (y en cualquier otro lugar donde haya una expresión) o cuando definamos ng-model.
- $ 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:
La primera es una función de observador que simplemente devuelve el objeto o simplemente podemos agregar una expresión.
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.
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();
}
};
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.
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.
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.
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.
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.
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
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.