Dirty Checking en AngularJS
Presentaremos cómo realizar comprobaciones sucias en AngularJs.
Implementar comprobación sucia en AngularJS
AngularJS realiza comprobaciones sucias en las variables $scope
para el enlace de datos bidireccional. Ember.js
realiza un enlace de datos bidireccional al arreglar mediante programación los setters y getters, pero en Angular, la verificación sucia le permite buscar variables que podrían estar disponibles o no.
# AngularJs
$scope.$watch( wExp, listener, objEq );
La función $scope.$watch
se usa cuando queremos ver cuándo se altera una variable. Tenemos que elegir tres argumentos para aplicar esta función: wExp
es lo que queremos ver, listener
es lo que queremos que haga cuando se actualice y si queremos comprobar una variable o un objeto.
Podemos omitir esto mientras usamos esta función cuando queremos verificar una variable. Veamos un ejemplo como se muestra a continuación.
#AngularJs
$scope.newName = 'Subhan';
$scope.$watch( function( ) {
return $scope.name;
}, function( newVal, oldVal ) {
console.log('$scope.newName is updated!');
} );
Angular guardará su función de observador en el $ alcance
. Puede comprobar esto iniciando sesión en $scope
en la consola.
En $watch
, también podemos utilizar una cadena en lugar de una función. Esto también funcionará igual que una función.
Cuando agregamos una cadena en el código fuente de Angular, el código será:
#AngularJs
if (typeof wExp == 'string' && get.constant) {
var newFn = watcher.fn;
watcher.fn = function(newVal, oldVal, scope) {
newFn.call(this, newVal, oldVal, scope);
arrayRemove(array, watcher);
};
}
Al aplicar este código, wExp
se establecerá como una función. Esto designará a nuestro oyente con la variable con nuestro nombre elegido.
la función $watchers
en AngularJS
Todos los observadores seleccionados se almacenarán en la variable $$watchers
en $scope
. Encontrará una variedad de objetos cuando marquemos $$watchers
, como se muestra a continuación.
#AngularJs
$$watchers = [
{
eq: false,
fn: function( newVal, oldVal ) {},
last: 'Subhan',
exp: function(){},
get: function(){}
}
];
La función unRegWatch
es devuelta por la función $watch
. Esto muestra que si queremos asignar el $scope.$watch
inicial a una variable, podemos lograrlo fácilmente ordenándole que deje de mirar.
Simplemente confirme que hemos abierto y mirado el primer $scope
registrado antes de desconectar el observador.
la función $scope.$apply
en AngularJS
Cuando intentamos ejecutar controlador/directiva/etc.
, Angular ejecuta una función dentro de la cual llamamos $scope.$watch
. Antes de administrar la función $digest
en rootScope
, la función $apply
ejecutará una función que hayamos elegido.
La función Angular $apply
es la siguiente:
#AngularJs
$apply: function(expr) {
try {
beginPhase('$apply');
return this.$eval(expr);
} catch (e) {
$exceptionHandler(e);
} finally {
clearPhase();
try {
$rootScope.$digest();
} catch (e) {
$exceptionHandler(e);
throw e;
}
}
}
el argumento expr
en AngularJS
Cuando usamos $scope.$apply
, a veces Angular o pasaríamos a través de una función, el argumento expr
es esa función.
Mientras usamos esta función, no encontraremos la necesidad de usar $apply
con más frecuencia. Veamos qué sucede cuando ng-keydown
usa $scope.$apply
.
Para enviar la directiva, usamos el siguiente código.
#AngularJs
var EventNgDirective = {};
forEach(
keydown keyup keypress submit focus blur copy cut paste'.split(' '),
function(newName) {
var newDirectiveName = newDirectiveNormalize('ng-' + newName);
EventNgDirective[newDirectiveName] = ['$parse', function($parse) {
return {
compile: function($element, attr) {
var fn = $parse(attr[directiveName]);
return function ngEventHandler(scope, element) {
element.on(lowercase(newName), function(event) {
scope.$apply(function() {
fn(scope, {$event:event});
});
});
};
}
};
}];
}
);
Este código recorrerá los tipos contrastantes de eventos que se pueden activar, generando una nueva directiva llamada ng(eventName)
. Cuando se trata de la función de compilación de la directiva, se registra un controlador de eventos en el elemento, donde el evento contiene el nombre de la directiva.
Cuando se activa este evento, Angular ejecutará $scope.$apply
, ofreciéndole una función para ejecutar.
Así es como el valor $scope
se actualizará con el valor del elemento, pero este enlace es solo un enlace unidireccional. La razón de esto es que hemos aplicado ng-keydown
, que nos permite cambiar solo cuando se aplica este evento.
¡Como resultado, se obtiene un nuevo valor!
Nuestro objetivo principal es lograr un enlace de datos bidireccional. Para lograr este objetivo, podemos utilizar el ng-model
.
Para que ng-model
funcione, utiliza tanto $scope.$watch
como $scope.$apply
.
La entrada que hemos dado se unirá al controlador de eventos por ng-model
. En este punto, ¡se llama $scope.$watch
!
$scope.$watch
se llama en el controlador de la directiva.
#AngularJs
$scope.$watch(function ngModelWatch() {
var value = ngModelGet($scope);
if (ctrl.$modelValue !== value) {
var formatters = ctrl.$formatters,
idx = formatters.length;
ctrl.$modelValue = value;
while(idx--) {
value = formatters[idx](value);
}
if (ctrl.$viewValue !== value) {
ctrl.$viewValue = value;
ctrl.$render();
}
}
return value;
});
Cuando se usa solo un argumento al configurar $scope.$watch
, la función que hemos elegido se aplicará aunque esté actualizada o no. La función presente en el ng-model
examina si el modelo y la vista están sincronizados.
Si no está sincronizado, la función le dará al modelo un nuevo valor y lo actualizará. Esta función nos permite saber cuál es el nuevo valor devolviendo el nuevo valor cuando ejecutamos esta función en $digest
.
Por qué no se dispara el Listener
Volvamos al punto del ejemplo en el que hemos desinscrito la función $scope.$watch
en la función similar a la que hemos descrito. Ahora podemos reconocer el motivo por el que no se nos notifica sobre la actualización de $scope.name
incluso cuando lo hayamos actualizado.
Como sabemos, Angular ejecuta $scope.$apply
en cada función de controlador de directiva. La función $scope.$apply
ejecutará $digest
solo con una condición cuando hayamos estimado la función de controlador de la directiva.
Esto significa que hemos cancelado la inscripción de la función $scope.$watch
antes de que pudiera funcionar, por lo que hay una posibilidad mínima o nula de que se la llame.
la función $digest
en AngularJS
Puede llamar a esta función en $rootScope
mediante $scope.$apply
. Podemos ejecutar el bucle de resumen en $rootScope
, y luego pasará por encima de los ámbitos y en su lugar ejecutará el bucle de resumen.
el bucle de resumen activará todas nuestras funciones wExp
en la variable $$watchers
. Los comparará con el último valor conocido.
Si los resultados son negativos, el oyente será despedido.
En el bucle de resumen, cuando se ejecuta, se repite dos veces. Una vez, recorrerá los observadores y luego volverá a repetirse hasta que el bucle ya no esté sucio
.
Cuando wExp
y el último valor conocido no son equivalentes, decimos que el bucle está sucio. Por lo general, este ciclo se ejecutará una vez y generará un error si se ejecuta más de diez veces.
Angular puede ejecutar $scope.$apply
en cualquier cosa que tenga la posibilidad de cambiar el valor de un modelo.
Tienes que ejecutar $scope.$apply();
cuando hemos actualizado $scope
fuera de Angular. Esto notificará a Angular que el alcance se ha actualizado.
Diseñemos una versión básica de verificación sucia.
Todos los datos que queramos guardar estarán en la función Scope
. Expandiremos el objeto en una función para duplicar $digest
y $watch
.
Debido a que no necesitamos evaluar ninguna función en relación con Scope
, no usaremos $apply
. Usaremos directamente $digest
.
Así es como se verá nuestro Scope
:
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( ) {
};
Scope.prototype.$digest = function( ) {
};
Hay dos parámetros, wExp
y oyente
, que $watch
debe adoptar. Podemos establecer los valores en Scope
.
Cuando se llama a $watch
, los enviamos al valor $$watcher
previamente almacenado en Scope
.
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( wExp, listener ) {
this.$$watchers.push( {
wExp: wExp,
listener: listener || function() {}
} );
};
Scope.prototype.$digest = function( ) {
};
Aquí notaremos que listener
se establece en una función vacía.
Puede registrar fácilmente un $watch
para todas las variables cuando no se ofrece ningún listener
siguiendo este ejemplo.
Ahora vamos a operar $digest
. Podemos inspeccionar si el valor anterior y el valor nuevo son equivalentes.
También podemos despedir al oyente si aún no lo está. Para lograr esto, haremos un bucle hasta que sean equivalentes.
La variable dirtyChecking
nos ha ayudado a conseguir este objetivo, independientemente de que los valores sean iguales.
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( wExp, listener ) {
this.$$watchers.push( {
wExp: wExp,
listener: listener || function() {}
} );
};
Scope.prototype.$digest = function( ) {
var dirtyChecking;
do {
dirtyChecking = false;
for( var i = 0; i < this.$$watchers.length; i++ ) {
var newVal = this.$$watchers[i].wExp(),
oldVal = this.$$watchers[i].last;
if( oldVal !== newVal ) {
this.$$watchers[i].listener(newVal, oldVal);
dirtyChecking = true;
this.$$watchers[i].last = newVal;
}
}
} while(dirtyChecking);
};
Ahora, diseñaremos un nuevo ejemplo de nuestro alcance. Asignaremos esto a $scope
; luego, registraremos una función de reloj.
Luego lo actualizaremos y lo digeriremos
.
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( wExp, listener ) {
this.$$watchers.push( {
wExp: wExp,
listener: listener || function() {}
} );
};
Scope.prototype.$digest = function( ) {
var dirtyChecking;
do {
dirtyChecking = false;
for( var i = 0; i < this.$$watchers.length; i++ ) {
var newVal = this.$$watchers[i].wExp(),
oldVal = this.$$watchers[i].last;
if( oldVal !== newVal ) {
this.$$watchers[i].listener(newVal, oldVal);
dirtyChecking = true;
this.$$watchers[i].last = newVal;
}
}
} while(dirtyChecking);
};
var $scope = new Scope();
$scope.newName = 'Subhan';
$scope.$watch(function(){
return $scope.newName;
}, function( newVal, oldVal ) {
console.log(newVal, oldVal);
} );
$scope.$digest();
Hemos implementado con éxito la verificación sucia. Observará sus registros cuando observemos la consola.
#AngularJs
Subhan undefined
Anteriormente, $scope.newName
no estaba definido. Lo hemos arreglado para Subhan
.
Conectaremos la función $digest
a un evento keyup
en una entrada. Al hacer esto, no tenemos que designarlo nosotros mismos.
Esto significa que también podemos lograr un enlace de datos bidireccional.
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( wExp, listener ) {
this.$$watchers.push( {
wExp: wExp,
listener: listener || function() {}
} );
};
Scope.prototype.$digest = function( ) {
var dirty;
do {
dirtyChecking = false;
for( var i = 0; i < this.$$watchers.length; i++ ) {
var newVal = this.$$watchers[i].wExp(),
oldVal = this.$$watchers[i].last;
if( oldVal !== newVal ) {
this.$$watchers[i].listener(newVal, oldVal);
dirtyChecking = true;
this.$$watchers[i].last = newVal;
}
}
} while(dirtyChecking);
};
var $scope = new Scope();
$scope.newName = 'Subhan';
var element = document.querySelectorAll('input');
element[0].onkeyup = function() {
$scope.newName = element[0].value;
$scope.$digest();
};
$scope.$watch(function(){
return $scope.newName;
}, function( newVal, oldVal ) {
console.log('Value updated In Input - The new value is ' + newVal);
element[0].value = $scope.newName;
} );
var updateScopeValue = function updateScopeValue( ) {
$scope.name = 'Butt';
$scope.$digest();
};
Con esta técnica, podemos actualizar fácilmente el valor de la entrada, como se ve en $scope.newName
. También puede llamar a updateScopeValue
, y el valor de entrada lo mostrará.
Rana is a computer science graduate passionate about helping people to build and diagnose scalable web application problems and problems developers face across the full-stack.
LinkedIn