angularjs - example - error:[$rootscope:inprog]
AngularJS: Previene el error $ digest ya en curso al llamar a $ scope. $ Apply() (25)
No utilice este patrón : esto terminará causando más errores de los que resuelve. Aunque pienses que arregla algo, no lo hizo.
Puede verificar si un $digest
ya está en curso marcando la $scope.$$phase
.
if(!$scope.$$phase) {
//$digest or $apply
}
$scope.$$phase
devolverá "$digest"
o "$apply"
si un $digest
o $apply
está en progreso. Creo que la diferencia entre estos estados es que $digest
procesará los relojes del alcance actual y sus hijos, y $apply
procesará a los observadores de todos los ámbitos.
Para el punto de @dnc253, si te encuentras llamando $digest
o $apply
frecuencia, es posible que lo estés haciendo mal. En general, encuentro que necesito digerir cuando necesito actualizar el estado del alcance como resultado de un evento DOM que se dispara fuera del alcance de Angular. Por ejemplo, cuando un modo bootstrap de twitter se oculta. A veces, el evento DOM se dispara cuando hay un $digest
en curso, a veces no. Por eso uso este cheque.
Me encantaría conocer una mejor manera si alguien la conoce.
De los comentarios: por @anddoutoi
- No lo haga
if (!$scope.$$phase) $scope.$apply()
, significa que$scope.$apply()
no es lo suficientemente alto en la pila de llamadas.
Estoy encontrando que necesito actualizar mi página a mi alcance manualmente cada vez más desde la construcción de una aplicación en angular.
La única forma que conozco de hacer esto es llamar a $apply()
desde el alcance de mis controladores y directivas. El problema con esto es que sigue arrojando un error a la consola que dice:
Error: $ digerido ya en progreso
¿Alguien sabe cómo evitar este error o lograr lo mismo pero de una manera diferente?
A veces, aún obtendrá errores si usa esta forma ( https://.com/a/12859093/801426 ).
Prueba esto:
if(! $rootScope.$root.$$phase) {
...
Consulte http://docs.angularjs.org/error/$rootScope:inprog
El problema surge cuando tiene una llamada a $apply
que a veces se ejecuta de forma asíncrona fuera del código Angular (cuando se debe usar $ apply) y, a veces, sincrónicamente, dentro del código Angular (que causa el error $digest already in progress
).
Esto puede suceder, por ejemplo, cuando tiene una biblioteca que obtiene elementos de forma asíncrona de un servidor y los almacena en caché. La primera vez que se solicita un elemento, se recuperará de forma asíncrona para no bloquear la ejecución del código. Sin embargo, la segunda vez, el elemento ya está en la memoria caché, por lo que se puede recuperar sincrónicamente.
La forma de evitar este error es asegurarse de que el código que llama a $apply
se ejecute de forma asíncrona. Esto se puede hacer ejecutando su código dentro de una llamada a $timeout
con el retraso establecido en 0
(que es el valor predeterminado). Sin embargo, llamar a su código dentro de $timeout
elimina la necesidad de llamar a $apply
, porque $ timeout activará otro ciclo de $digest
por sí solo, lo que, a su vez, hará todas las actualizaciones necesarias, etc.
Solución
En resumen, en lugar de hacer esto:
... your controller code...
$http.get(''some/url'', function(data){
$scope.$apply(function(){
$scope.mydate = data.mydata;
});
});
... more of your controller code...
hacer esto:
... your controller code...
$http.get(''some/url'', function(data){
$timeout(function(){
$scope.mydate = data.mydata;
});
});
... more of your controller code...
Solo llame a $apply
cuando sepa que el código que se está ejecutando siempre se ejecutará fuera del código Angular (por ejemplo, su llamada a $ apply ocurrirá dentro de una devolución de llamada que se llama mediante un código fuera de su código Angular).
A menos que alguien esté al tanto de una desventaja impactante al usar $timeout
sobre $apply
, no veo por qué no siempre podría usar $timeout
(sin demora) en lugar de $apply
, ya que hará aproximadamente lo mismo.
Cuando recibe este error, básicamente significa que ya está en proceso de actualizar su vista. Realmente no debería necesitar llamar a $apply()
desde su controlador. Si su vista no se actualiza como esperaría, y luego recibe este error después de llamar a $apply()
, lo más probable es que no esté actualizando el modelo correctamente. Si publicas algunos detalles, podríamos resolver el problema central.
De una discusión reciente con los chicos de Angular sobre este mismo tema: por razones de pruebas futuras, no debe usar la $$phase
Cuando se presiona para la forma "correcta" de hacerlo, la respuesta es actualmente
$timeout(function() {
// anything you want can go here and will safely be run on the next digest.
})
Recientemente me encontré con esto al escribir servicios angulares para envolver las API de Facebook, Google y Twitter que, en diversos grados, han recibido devoluciones de llamadas.
Aquí hay un ejemplo desde dentro de un servicio. (En aras de la brevedad, el resto del servicio, que configuró las variables, inyectó $ timeout, etc., se ha dejado de lado).
window.gapi.client.load(''oauth2'', ''v2'', function() {
var request = window.gapi.client.oauth2.userinfo.get();
request.execute(function(response) {
// This happens outside of angular land, so wrap it in a timeout
// with an implied apply and blammo, we''re in action.
$timeout(function() {
if(typeof(response[''error'']) !== ''undefined''){
// If the google api sent us an error, reject the promise.
deferred.reject(response);
}else{
// Resolve the promise with the whole response if ok.
deferred.resolve(response);
}
});
});
});
Tenga en cuenta que el argumento de demora para $ timeout es opcional y se establecerá de manera predeterminada en 0 si no se configura ( $timeout calls calls $browser.defer .
Un poco no intuitivo, pero esa es la respuesta de los chicos que escriben Angular, ¡así que es lo suficientemente bueno para mí!
Debe usar $ evalAsync o $ timeout según el contexto.
Este es un enlace con una buena explicación:
http://www.bennadel.com/blog/2605-scope-evalasync-vs-timeout-in-angularjs.htm
El ciclo de digestión es una llamada síncrona. No cederá el control del bucle de eventos del navegador hasta que esté terminado. Hay algunas maneras de lidiar con esto. La forma más fácil de lidiar con esto es usar el tiempo de espera de $ construido, y una segunda forma es si está usando el guión bajo o el valor de mayúscula (y debería estarlo), llame a lo siguiente:
$timeout(function(){
//any code in here will automatically have an apply run afterwards
});
o si tiene subrayado:
_.defer(function(){$scope.$apply();});
Probamos varias soluciones alternativas y odiamos inyectar $ rootScope en todos nuestros controladores, directivas e incluso en algunas fábricas. Entonces, $ timeout y _.defer han sido nuestros favoritos hasta ahora. Estos métodos le dicen a angular que espere hasta el próximo ciclo de animación, lo que garantizará que se termine el alcance actual. $ Apply.
En primer lugar, no lo arregles de esta manera
if ( ! $scope.$$phase) {
$scope.$apply();
}
No tiene sentido porque $ phase es solo una bandera booleana para el ciclo $ digest, por lo que su $ apply () a veces no se ejecuta. Y recuerda que es una mala práctica.
En su lugar, usa $timeout
$timeout(function(){
// Any code in here will automatically have an $scope.apply() run afterwards
$scope.myvar = newValue;
// And it just works!
});
Si está usando el guión bajo o el dato, puede usar defer ():
_.defer(function(){
$scope.$apply();
});
Encontré esto: https://coderwall.com/p/ngisma donde Nathan Walker (cerca de la parte inferior de la página) sugiere un decorador en $ rootScope para crear la función ''safeApply'', código:
yourAwesomeModule.config([
''$provide'', function($provide) {
return $provide.decorator(''$rootScope'', [
''$delegate'', function($delegate) {
$delegate.safeApply = function(fn) {
var phase = $delegate.$$phase;
if (phase === "$apply" || phase === "$digest") {
if (fn && typeof fn === ''function'') {
fn();
}
} else {
$delegate.$apply(fn);
}
};
return $delegate;
}
]);
}
]);
Entendiendo que los documentos de Angular llaman a la comprobación de la $$phase
y al anti-patrón , traté de obtener $timeout
y _.defer
to work.
El tiempo de espera y los métodos diferidos crean un flash de contenido {{myVar}}
en el dom como un FOUT . Para mí esto no era aceptable. Me deja sin mucho que decir dogmáticamente que algo es un hack y que no tenga una alternativa adecuada.
Lo único que funciona cada vez es:
if(scope.$$phase !== ''$digest''){ scope.$digest() }
.
No entiendo el peligro de este método, o por qué se describe como un hack por parte de las personas en los comentarios y el equipo angular. El comando parece preciso y fácil de leer:
"Haz el resumen a menos que uno ya esté sucediendo"
En CoffeeScript es aún más bonito:
scope.$digest() unless scope.$$phase is ''$digest''
¿Cuál es el problema con esto? ¿Hay alguna alternativa que no cree un FOUT? https://github.com/yearofmoo/AngularJS-Scope.SafeApply ve bien, pero también usa el método de inspección de $$phase
.
Este es mi servicio de utilidad.
angular.module(''myApp'', []).service(''Utils'', function Utils($timeout) {
var Super = this;
this.doWhenReady = function(scope, callback, args) {
if(!scope.$$phase) {
if (args instanceof Array)
callback.apply(scope, Array.prototype.slice.call(args))
else
callback();
}
else {
$timeout(function() {
Super.doWhenReady(scope, callback, args);
}, 250);
}
};
});
Y este es un ejemplo de su uso:
angular.module(''myApp'').controller(''MyCtrl'', function ($scope, Utils) {
$scope.foo = function() {
// some code here . . .
};
Utils.doWhenReady($scope, $scope.foo);
$scope.fooWithParams = function(p1, p2) {
// some code here . . .
};
Utils.doWhenReady($scope, $scope.fooWithParams, [''value1'', ''value2'']);
};
Esto solucionará tu problema:
if(!$scope.$$phase) {
//TODO
}
He estado usando este método y parece funcionar perfectamente bien. Esto solo espera el momento en que el ciclo ha terminado y luego los activadores se apply()
. Simplemente llame a la función apply(<your scope>)
desde cualquier lugar que desee.
function apply(scope) {
if (!scope.$$phase && !scope.$root.$$phase) {
scope.$apply();
console.log("Scope Apply Done !!");
}
else {
console.log("Scheduling Apply after 200ms digest cycle already in progress");
setTimeout(function() {
apply(scope)
}, 200);
}
}
He podido resolver este problema llamando a $eval
lugar de $apply
en lugares donde sé que se ejecutará la función $digest
.
Según los docs , $apply
básicamente hace esto:
function $apply(expr) {
try {
return $eval(expr);
} catch (e) {
$exceptionHandler(e);
} finally {
$root.$digest();
}
}
En mi caso, un ng-click
cambia una variable dentro de un ámbito, y un $ watch en esa variable cambia otras variables que deben $applied
. Este último paso provoca el error "compendio ya en progreso".
Al reemplazar $apply
con $eval
dentro de la expresión de observación, las variables de alcance se actualizan como se espera.
Por lo tanto, parece que si el resumen se va a ejecutar de cualquier manera debido a algún otro cambio en Angular, $eval
''ing es todo lo que necesita hacer.
La forma más corta de $apply
seguro es:
$timeout(angular.noop)
Le aconsejaría que utilice un evento personalizado en lugar de desencadenar un ciclo de resumen.
Descubrí que transmitir eventos personalizados y registrar escuchas para estos eventos es una buena solución para desencadenar una acción que desea que ocurra, ya sea que se encuentre o no en un ciclo de resumen.
Al crear un evento personalizado, también está siendo más eficiente con su código porque solo está activando escuchas suscritas a dicho evento y NO está activando todos los relojes vinculados al alcance como lo haría si invocara el alcance. $ Apply.
$scope.$on(''customEventName'', function (optionalCustomEventArguments) {
//TODO: Respond to event
});
$scope.$broadcast(''customEventName'', optionalCustomEventArguments);
Muchas de las respuestas aquí contienen buenos consejos, pero también pueden llevar a confusión. Simplemente usar $timeout
no es la mejor ni la solución correcta. Además, asegúrese de leer eso si está preocupado por el rendimiento o la escalabilidad.
Cosas que debes saber
$$phase
es privada para el marco y hay buenas razones para ello.$timeout(callback)
esperará hasta que se complete el ciclo de compendio actual (si corresponde), luego ejecutará la devolución de llamada, luego ejecutará al final una aplicación completa de$apply
.$timeout(callback, delay, false)
hará lo mismo (con un retraso opcional antes de ejecutar la devolución de llamada), pero no activará un$apply
(tercer argumento) que guarda el rendimiento si no modificó su modelo Angular ($ scope ).$scope.$apply(callback)
invoca, entre otras cosas,$rootScope.$digest
, lo que significa que redigerá el alcance raíz de la aplicación y todos sus elementos secundarios, incluso si está dentro de un alcance aislado.$scope.$digest()
simplemente sincronizará su modelo con la vista, pero no digerirá su alcance principal, lo que puede ahorrar un montón de actuaciones cuando se trabaja en una parte aislada de su HTML con un alcance aislado (principalmente de una directiva) . $ digest no recibe una devolución de llamada: usted ejecuta el código, luego digiere.$scope.$evalAsync(callback)
se ha introducido con angularjs 1.2, y probablemente resolverá la mayoría de sus problemas. Por favor, consulte el último párrafo para obtener más información al respecto.Si obtiene el
$digest already in progress error
, entonces su arquitectura es incorrecta: no necesita redigir su alcance o no debería estar a cargo de eso (ver más abajo).
Cómo estructurar tu código
Cuando recibe ese error, intenta digerir su alcance mientras está en progreso: dado que no conoce el estado de su alcance en ese momento, no está a cargo de lidiar con su digestión.
function editModel() {
$scope.someVar = someVal;
/* Do not apply your scope here since we don''t know if that
function is called synchronously from Angular or from an
asynchronous code */
}
// Processed by Angular, for instance called by a ng-click directive
$scope.applyModelSynchronously = function() {
// No need to digest
editModel();
}
// Any kind of asynchronous code, for instance a server request
callServer(function() {
/* That code is not watched nor digested by Angular, thus we
can safely $apply it */
$scope.$apply(editModel);
});
Y si sabe lo que está haciendo y está trabajando en una directiva pequeña aislada mientras forma parte de una gran aplicación Angular, podría preferir $ digest en lugar de $ aplicar para guardar actuaciones.
Actualización desde Angularjs 1.2
Se ha agregado un método nuevo y poderoso a cualquier $ scope: $evalAsync
. Básicamente, ejecutará su devolución de llamada dentro del ciclo de resumen actual si se está produciendo, de lo contrario, un nuevo ciclo de resumen comenzará a ejecutar la devolución de llamada.
Eso todavía no es tan bueno como un $scope.$digest
Si realmente sabe que solo necesita sincronizar una parte aislada de su HTML (ya que se activará un nuevo $apply
si no hay ninguno en curso), pero esto es lo mejor Solución cuando está ejecutando una función que no puede saber si se ejecutará de forma síncrona o no , por ejemplo, después de obtener un recurso potencialmente almacenado en caché: a veces esto requerirá una llamada asíncrona a un servidor, de lo contrario, el recurso se buscará de forma local y sincrónica.
En estos casos y en todos los demás en los que tuvo una !$scope.$$phase
, asegúrese de usar $scope.$evalAsync( callback )
Puedes usar
$timeout
para evitar el error.
$timeout(function () {
var scope = angular.element($("#myController")).scope();
scope.myMethod();
scope.$scope();
},1);
También puede utilizar evalAsync. Se ejecutará en algún momento después de que el resumen ha terminado!
scope.evalAsync(function(scope){
//use the scope...
});
Tuve el mismo problema con los scripts de terceros como CodeMirror, por ejemplo, y Krpano, e incluso utilizando los métodos de SafeApply mencionados aquí no he resuelto el error.
Pero lo que sí resolvió es usar el servicio de tiempo de espera de $ (no se olvide de inyectarlo primero).
Así, algo como:
$timeout(function() {
// run my code safely here
})
y si dentro de tu código estás usando
esta
tal vez porque está dentro del controlador de una directiva de fábrica o simplemente necesita algún tipo de enlace, entonces usted haría algo como:
.factory(''myClass'', [
''$timeout'',
function($timeout) {
var myClass = function() {};
myClass.prototype.surprise = function() {
// Do something suprising! :D
};
myClass.prototype.beAmazing = function() {
// Here ''this'' referes to the current instance of myClass
$timeout(angular.bind(this, function() {
// Run my code safely here and this is not undefined but
// the same as outside of this anonymous function
this.surprise();
}));
}
return new myClass();
}]
)
Un método pequeño y práctico para mantener este proceso SECO:
function safeApply(scope, fn) {
(scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
}
intenta usar
$scope.applyAsync(function() {
// your code
});
en lugar de
if(!$scope.$$phase) {
//$digest or $apply
}
$ applyAsync Programe la invocación de $ apply para que ocurra más adelante. Esto se puede usar para poner en cola varias expresiones que deben evaluarse en el mismo resumen.
NOTA: Dentro del $ digest, $ applyAsync () solo se vaciará si el alcance actual es $ rootScope. Esto significa que si llama a $ digest en un ámbito secundario, no vaciará implícitamente la cola $ applyAsync ().
Ejemplo:
$scope.$applyAsync(function () {
if (!authService.authenticated) {
return;
}
if (vm.file !== null) {
loadService.setState(SignWizardStates.SIGN);
} else {
loadService.setState(SignWizardStates.UPLOAD_FILE);
}
});
Referencias:
1. Scope. $ ApplyAsync () vs. Scope. $ EvalAsync () en AngularJS 1.3
similar a las respuestas anteriores, pero esto me ha funcionado fielmente ... en un servicio agrega:
//sometimes you need to refresh scope, use this to prevent conflict
this.applyAsNeeded = function (scope) {
if (!scope.$$phase) {
scope.$apply();
}
};
usar $scope.$$phase || $scope.$apply();
$scope.$$phase || $scope.$apply();
en lugar
yearofmoo hizo un gran trabajo al crear una función $ safeApply reutilizable para nosotros:
Uso:
//use by itself
$scope.$safeApply();
//tell it which scope to update
$scope.$safeApply($scope);
$scope.$safeApply($anotherScope);
//pass in an update function that gets called when the digest is going on...
$scope.$safeApply(function() {
});
//pass in both a scope and a function
$scope.$safeApply($anotherScope,function() {
});
//call it on the rootScope
$rootScope.$safeApply();
$rootScope.$safeApply($rootScope);
$rootScope.$safeApply($scope);
$rootScope.$safeApply($scope, fn);
$rootScope.$safeApply(fn);