Estoy descubriendo que necesito actualizar mi página a mi alcance manualmente más y más desde la creación de una aplicación en angular.
La única forma en que sé hacer esto es llamar $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: $ digest ya está en progreso
¿Alguien sabe cómo evitar este error o lograr lo mismo pero de una manera diferente?
angularjs
angularjs-scope
angularjs-digest
Bombilla1
fuente
fuente
$timeout()
ng-*
). Asegúrese de que, si lo está llamando desde una función (que se llama a través de timeout / ajax / events), que no se ejecute también en carga inicialmente.Respuestas:
Puede verificar si a
$digest
ya está en progreso al verificar$scope.$$phase
.$scope.$$phase
volverá"$digest"
o"$apply"
si a$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 con$apply
frecuencia, puedes estar haciéndolo mal. Generalmente 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 modal de arranque de Twitter se oculta. A veces, el evento DOM se dispara cuando a$digest
está en progreso, a veces no. Por eso uso este cheque.Me encantaría conocer una mejor manera si alguien conoce una.
De los comentarios: por @anddoutoi
Angular.js Anti Patrones
fuente
if (!$scope.$$phase) $scope.$apply()
", github.com/angular/angular.js/wiki/Anti-PatternsDe una discusión reciente con los chicos de Angular sobre este mismo tema: por razones a prueba de futuro, no debes usar
$$phase
Cuando se presiona para la forma "correcta" de hacerlo, la respuesta es actualmente
Recientemente me encontré con esto al escribir servicios angulares para envolver las API de Facebook, Google y Twitter que, en diversos grados, reciben devoluciones de llamadas.
Aquí hay un ejemplo desde dentro de un servicio. (En aras de la brevedad, el resto del servicio, que configuró variables, inyectó $ timeout, etc., se ha dejado).
Tenga en cuenta que el argumento de retraso para $ timeout es opcional y su valor predeterminado será 0 si no se establece ( $ timeout llama a $ browser.defer, que por defecto es 0 si no se establece el retraso )
Un poco no intuitivo, pero esa es la respuesta de los chicos que escriben Angular, ¡así que es lo suficientemente bueno para mí!
fuente
$timeout
lugar de nativosetTimeout
, ¿por qué no usa en$window
lugar del nativowindow
?$timeout
en este caso es que$timeout
garantiza que el alcance angular se actualice correctamente. Si un $ digest no está en progreso, hará que se ejecute un nuevo $ digest.cancel
ello. De los documentos : "Como resultado de esto, la promesa se resolverá con un rechazo". No puede resolver una promesa resuelta. Su cancelación no causará ningún error, pero tampoco hará nada positivo.El ciclo de resumen es una llamada síncrona. No cederá el control al bucle de eventos del navegador hasta que termine. Hay algunas formas de lidiar con esto. La forma más fácil de lidiar con esto es usar el tiempo de espera incorporado de $, y una segunda forma es si está usando subrayado o lodash (y debería hacerlo), llame al siguiente:
o si tienes lodash:
Intentamos varias soluciones y odiamos inyectar $ rootScope en todos nuestros controladores, directivas e incluso 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 el alcance actual. $ Apply haya terminado.
fuente
underscore.js
. Esta solución no vale la pena importar toda la biblioteca de subrayado solo para usar sudefer
función. Prefiero la$timeout
solución porque todos ya tienen acceso a$timeout
través de angular, sin ninguna dependencia de otras bibliotecas.Muchas de las respuestas aquí contienen buenos consejos, pero también pueden generar confusión. Simplemente usar no
$timeout
es la mejor solución ni la mejor. Además, asegúrese de leer eso si le preocupan las actuaciones o la escalabilidad.Cosas que deberías saber
$$phase
es privado para el marco y hay buenas razones para ello.$timeout(callback)
esperará hasta que se complete el ciclo de resumen actual (si lo hay), luego ejecutará la devolución de llamada, luego se ejecutará al final un completo$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 ahorre actuaciones si no modificó su modelo Angular ($ scope).$scope.$apply(callback)
invoca, entre otras cosas, lo$rootScope.$digest
que significa que redirigirá 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 asimilará el alcance de sus padres, lo que puede ahorrar muchas interpretaciones al trabajar en una parte aislada de su HTML con un alcance aislado (principalmente de una directiva). $ digest no recibe una devolución de llamada: ejecuta el código y luego resume.$scope.$evalAsync(callback)
se ha introducido con angularjs 1.2 y probablemente resolverá la mayoría de sus problemas. Consulte el último párrafo para obtener más información al respecto.si obtienes el
$digest already in progress error
, entonces tu arquitectura está equivocada: o no necesitas rediseñar tu alcance, o no deberías estar a cargo de eso (ver más abajo).Cómo estructurar tu código
Cuando recibe ese error, está tratando de digerir su alcance mientras ya 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.
Y si sabe lo que está haciendo y trabajando en una pequeña directiva 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
Un nuevo método, de gran alcance ha sido añadido a cualquier ámbito $:
$evalAsync
. Básicamente, ejecutará su devolución de llamada dentro del ciclo de resumen actual si está ocurriendo uno; de lo contrario, un nuevo ciclo de resumen comenzará a ejecutar la devolución de llamada.Todavía no es tan bueno como
$scope.$digest
si realmente supiera que solo necesita sincronizar una parte aislada de su HTML (ya$apply
que se activará una nueva si no hay ninguna en progreso), pero esta es la mejor solución cuando está ejecutando una función que no puede saber si se ejecutará sincrónicamente o no , por ejemplo, después de recuperar un recurso potencialmente almacenado en caché: a veces esto requerirá una llamada asíncrona a un servidor; de lo contrario, el recurso se recuperará localmente sincrónicamente.En estos casos y en todos los demás donde tuvo un
!$scope.$$phase
, asegúrese de usar$scope.$evalAsync( callback )
fuente
$timeout
es criticado de pasada. ¿Puedes dar más razones para evitar$timeout
?Práctico método de ayuda para mantener este proceso SECO:
fuente
scope.$apply(fn);
debería serscope.$apply(fn());
porque fn () ejecutará la función y no fn. Por favorTuve el mismo problema con scripts de terceros como CodeMirror, por ejemplo, y Krpano, e incluso el uso de los métodos safeApply mencionados aquí no me ha resuelto el error.
Pero lo que sí ha resuelto es usar el servicio $ timeout (no olvides inyectarlo primero).
Por lo tanto, algo como:
y si dentro de tu código estás usando
quizás porque está dentro del controlador de una directiva de fábrica o simplemente necesita algún tipo de enlace, entonces haría algo como:
fuente
Ver http://docs.angularjs.org/error/$rootScope:inprog
El problema surge cuando tiene una llamada
$apply
que a veces se ejecuta de forma asincrónica fuera del código angular (cuando se debe usar $ apply) y, a veces, sincrónicamente dentro del código angular (que causa el$digest already in progress
error).Esto puede suceder, por ejemplo, cuando tiene una biblioteca que recupera asincrónicamente elementos de un servidor y los almacena en caché. La primera vez que se solicita un elemento, se recuperará de forma asincrónica para no bloquear la ejecución del código. La segunda vez, sin embargo, el elemento ya está en la memoria caché, por lo que puede recuperarse sincrónicamente.
La forma de evitar este error es asegurarse de que el código que llama
$apply
se ejecute de forma asincrónica. Esto se puede hacer ejecutando su código dentro de una llamada a$timeout
con el retraso establecido en0
(que es el valor predeterminado). Sin embargo, llamar a su código adentro$timeout
elimina la necesidad de llamar$apply
, porque $ timeout activará otro$digest
ciclo por sí solo, lo que, a su vez, hará todas las actualizaciones necesarias, etc.Solución
En resumen, en lugar de hacer esto:
hacer esto:
Solo llamar
$apply
cuando sepa que el código en ejecución siempre se ejecutará fuera del código angular (por ejemplo, su llamada a $ apply se realizará dentro de una devolución de llamada que se llama por código fuera de su código angular).A menos que alguien sea consciente de alguna desventaja impactante al usarlo en
$timeout
exceso$apply
, no veo por qué no siempre puede usarlo$timeout
(con cero retraso) en lugar de hacerlo$apply
, ya que hará aproximadamente lo mismo.fuente
$apply
me llamo a mí mismo pero sigo recibiendo el error.$apply
es síncrono (su devolución de llamada se ejecuta, luego el código que sigue a $ apply) mientras$timeout
que no lo es: el código actual después del tiempo de espera se ejecuta, luego una nueva pila comienza con su devolución de llamada, como si estuviera usandosetTimeout
. Eso podría generar fallas gráficas si actualizara dos veces el mismo modelo:$timeout
esperará a que la vista se actualice antes de actualizarla nuevamente.Cuando obtiene este error, básicamente significa que ya está en el proceso de actualizar su vista. Realmente no debería necesitar llamar
$apply()
dentro de su controlador. Si su vista no se actualiza como es de esperar, y luego recibe este error después de llamar$apply()
, lo más probable es que no esté actualizando el modelo correctamente. Si publica algunos detalles, podríamos resolver el problema central.fuente
you're not updating the the model correctly
?$scope.err_message = 'err message';
no es la actualización correcta?$apply()
es cuando actualiza el modelo "fuera" de angular (por ejemplo, desde un complemento jQuery). Es fácil caer en la trampa de la vista que no se ve bien, por lo que arroja un montón de$apply()
s en todas partes, lo que luego termina con el error visto en el OP. Cuando digoyou're not updating the the model correctly
que solo quiero decir que toda la lógica de negocios no llena correctamente nada que pueda estar en el alcance, lo que lleva a que la vista no se vea como se esperaba.La forma más corta de caja fuerte
$apply
es:fuente
También puede usar evalAsync. ¡Funcionará en algún momento después de que el resumen haya terminado!
fuente
Antes que nada, no lo arregles de esta manera
No tiene sentido porque $ phase es solo una bandera booleana para el ciclo de $ digest, por lo que su $ apply () a veces no se ejecutará. Y recuerda que es una mala práctica.
En cambio, use
$timeout
Si está usando subrayado o lodash, puede usar defer ():
fuente
A veces, aún obtendrá errores si lo usa de esta manera ( https://stackoverflow.com/a/12859093/801426 ).
Prueba esto:
fuente
$rootScope
yanyScope.$root
son el mismo chico$rootScope.$root
es redundanteDebe usar $ evalAsync o $ timeout según el contexto.
Este es un enlace con una buena explicación:
fuente
intenta usar
en vez de
$ applyAsync Programe la invocación de $ apply para que ocurra más adelante. Esto se puede usar para poner en cola múltiples expresiones que deben evaluarse en el mismo resumen.
NOTA: Dentro de $ 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:
Referencias
1. Scope. $ ApplyAsync () vs. Scope. $ EvalAsync () en AngularJS 1.3
fuente
Le aconsejaría que use un evento personalizado en lugar de desencadenar un ciclo de resumen.
He llegado a la conclusión de que transmitir eventos personalizados y registrar oyentes para estos eventos es una buena solución para activar una acción que desea que se produzca, esté 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 activa los oyentes suscritos a dicho evento y NO activa todos los relojes vinculados al alcance como lo haría si invocara el alcance. $ Apply.
fuente
yearofmoo hizo un gran trabajo al crear una función reutilizable $ safeApply para nosotros:
Uso:
fuente
He podido resolver este problema llamando en
$eval
lugar de$apply
en lugares donde sé que la$digest
función se ejecutará.Según los documentos ,
$apply
básicamente hace esto:En mi caso, un
ng-click
cambia una variable dentro de un ámbito, y un $ watch en esa variable cambia otras variables que tienen que ser$applied
. Este último paso provoca el error "resumen ya en progreso".Al reemplazar
$apply
con$eval
dentro de la expresión de observación, las variables de alcance se actualizan como se esperaba.Por lo tanto, parece que si el resumen se va a ejecutar de todos modos debido a algún otro cambio dentro de Angular,
$eval
ing es todo lo que necesita hacer.fuente
utilizar
$scope.$$phase || $scope.$apply();
en su lugarfuente
La comprensión de que los documentos angular llaman comprobar el
$$phase
un anti-patrón , traté de conseguir$timeout
y_.defer
de trabajo.El tiempo de espera y los métodos diferidos crean un destello de
{{myVar}}
contenido no analizado en el dom como un FOUT . Para mí esto no era aceptable. Me deja sin mucho que decir dogmáticamente que algo es un truco y no tiene 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é es descrito como un hack por personas en los comentarios y el equipo angular. El comando parece preciso y fácil de leer:
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? $ safeApply se ve bien pero también usa el
$$phase
método de inspección.fuente
Este es mi servicio de utilidades:
y este es un ejemplo de su uso:
fuente
He estado usando este método y parece funcionar perfectamente bien. Esto solo espera el tiempo que el ciclo ha terminado y luego se dispara
apply()
. Simplemente llame a la funciónapply(<your scope>)
desde cualquier lugar que desee.fuente
Cuando desactivé el depurador, el error ya no ocurre. En mi caso , fue porque el depurador detuvo la ejecución del código.
fuente
similar a las respuestas anteriores, pero esto ha funcionado fielmente para mí ... en un servicio agregue:
fuente
Puedes usar
para evitar el error
fuente
Básicamente, el problema surge cuando, estamos solicitando angular para ejecutar el ciclo de resumen, aunque está en proceso, lo que está creando un problema angular para la comprensión. consecuencia excepción en consola.
1. No tiene ningún sentido llamar a scope. $ Apply () dentro de la función $ timeout porque internamente hace lo mismo.
2. El código va con la función JavaScript vainilla porque su definición angular no angular es nativa, es decir, setTimeout
3. Para ello, puede utilizar
if (!
Scope . $$ phase) { scope. $ EvalAsync (function () {
}); }
fuente
Aquí hay una buena solución para evitar este error y evitar $ apply
puede combinar esto con debounce (0) si llama en función de un evento externo. Arriba está el 'rebote' que estamos usando, y un ejemplo completo de código
y el código en sí para escuchar algún evento y llamar a $ digest solo en $ scope que necesita
fuente
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 el func 'safeApply', código:
fuente
Esto resolverá tu problema:
fuente