AngularJS: Evite el error $ digest ya en progreso al llamar a $ scope. $ Apply ()

838

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?

Bombilla1
fuente
34
Es realmente frustrante que necesitemos usar $ apply cada vez más.
OZ_
También recibo este error, aunque llamo $ apply en una devolución de llamada. Estoy usando una biblioteca de terceros para acceder a los datos en sus servidores, por lo que no puedo aprovechar $ http, ni quiero hacerlo, ya que tendría que volver a escribir su biblioteca para usar $ http.
Trevor
45
uso$timeout()
Onur Yıldırım
66
use $ timeout (fn) + 1, puede solucionar el problema,! $ scope. $$ phase no es la mejor solución.
Huei Tan
1
Solo ajuste del código / alcance de la llamada. $ Se aplica desde dentro de tiempos de espera (no $ tiempo de espera) Funciones AJAX (no $ http) y eventos (no 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.
Patrick

Respuestas:

660

No use este patrón : esto terminará causando más errores de los que resuelve. Aunque creas que solucionó algo, no lo hizo.

Puede verificar si a $digestya está en progreso al verificar $scope.$$phase.

if(!$scope.$$phase) {
  //$digest or $apply
}

$scope.$$phasevolverá "$digest"o "$apply"si a $digesto $applyestá en progreso. Creo que la diferencia entre estos estados es que $digestprocesará los relojes del alcance actual y sus hijos, y $applyprocesará a los observadores de todos los ámbitos.

Para el punto de @ dnc253, si te encuentras llamando $digesto con $applyfrecuencia, 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 $digestestá 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

  1. No lo hagas if (!$scope.$$phase) $scope.$apply(), significa que tu $scope.$apply()no es lo suficientemente alto en la pila de llamadas.
Sotavento
fuente
230
Me parece que $ digest / $ apply debería hacer esto por defecto
Roy Truelove
21
Tenga en cuenta que en algunos casos tengo que verificar pero el alcance actual Y el alcance raíz. He estado obteniendo un valor para la fase $$ en la raíz, pero no en mi alcance. Creo que tiene algo que ver con el alcance aislado de una directiva, pero ...
Roy Truelove
106
"Deja de hacerlo if (!$scope.$$phase) $scope.$apply()", github.com/angular/angular.js/wiki/Anti-Patterns
anddoutoi
34
@anddoutoi: De acuerdo; Su enlace deja bastante claro que esta no es la solución; sin embargo, no estoy seguro de qué se entiende por "usted no es lo suficientemente alto en la pila de llamadas". ¿Sabes qué significa esto?
Trevor
13
@threed: mira la respuesta de aaronfrost. La forma correcta es usar diferir para activar el resumen en el siguiente ciclo. De lo contrario, el evento se perderá y no actualizará el alcance en absoluto.
Marek
663

De 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

$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, 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).

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 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í!

betaorbust
fuente
55
Me he encontrado con esto muchas veces en mis directivas. Estaba escribiendo uno para redactor y esto resultó funcionar perfectamente. Estuve en una reunión con Brad Green y dijo que Angular 2.0 será enorme sin un ciclo de digestión usando la capacidad de observación nativa de JS y usando un polyfill para navegadores que carecen de eso. En ese punto ya no necesitaremos hacer esto. :)
Michael J. Calkins
Ayer he visto un problema en el que llamar a selectize.refreshItems () dentro de $ timeout causó el temido error de resumen recursivo. ¿Alguna idea de cómo podría ser?
iwein
3
Si usa en $timeoutlugar de nativo setTimeout, ¿por qué no usa en $windowlugar del nativo window?
LeeGee
2
@LeeGee: El punto de uso $timeouten este caso es que $timeoutgarantiza que el alcance angular se actualice correctamente. Si un $ digest no está en progreso, hará que se ejecute un nuevo $ digest.
asombro
2
@webicy Eso no es una cosa. Cuando se ejecuta el cuerpo de la función pasada a $ timeout, ¡la promesa ya está resuelta! No hay absolutamente ninguna razón para cancelello. 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.
daemonexmachina
324

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:

$timeout(function(){
    //any code in here will automatically have an apply run afterwards
});

o si tienes lodash:

_.defer(function(){$scope.$apply();});

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.

escarchado
fuente
2
¿Es comparable a usar $ timeout (...)? He usado $ timeout en varios casos para diferir al siguiente ciclo de eventos y parece funcionar bien. ¿Alguien sabe si hay alguna razón para no usar $ timeout?
Trevor
99
Esto realmente solo debe usarse si ya lo está usando underscore.js. Esta solución no vale la pena importar toda la biblioteca de subrayado solo para usar su deferfunción. Prefiero la $timeoutsolución porque todos ya tienen acceso a $timeouttravés de angular, sin ninguna dependencia de otras bibliotecas.
tennisgent
10
Es cierto ... pero si no está usando subrayado o lodash ... necesita reevaluar lo que está haciendo. Esas dos bibliotecas han cambiado la apariencia de ese código.
helado
2
Tenemos lodash como una dependencia de Restangular (pronto eliminaremos Restangular a favor de ng-route). Creo que es una buena respuesta, pero no es bueno asumir que la gente quiere usar el subrayado / lodash. Por supuesto, esas bibliotecas están bien ... si las utiliza lo suficiente ... en estos días uso métodos ES5 que eliminan el 98% de la razón por la que solía incluir guiones bajos.
BradGreens
2
Tienes razón @SgtPooki. Modifiqué la respuesta para incluir la opción de usar $ timeout también. $ timeout y _.defer esperarán hasta el próximo ciclo de animación, lo que garantizará que el alcance actual. $ apply haya finalizado. Gracias por mantenerme honesto y hacerme actualizar la respuesta aquí.
helado
267

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.$digestque 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.

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 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.$digestsi realmente supiera que solo necesita sincronizar una parte aislada de su HTML (ya $applyque 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 )

floribon
fuente
44
$timeoutes criticado de pasada. ¿Puedes dar más razones para evitar $timeout?
mlhDev
88

Práctico método de ayuda para mantener este proceso SECO:

function safeApply(scope, fn) {
    (scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
}
lambinator
fuente
66
Your safeApply me ayudó a comprender lo que estaba sucediendo mucho más que cualquier otra cosa. Gracias por publicar eso.
Jason More
44
Estaba a punto de hacer lo mismo, pero ¿no significa que existe la posibilidad de que $ digest no vea los cambios que hacemos en fn ()? ¿No sería mejor retrasar la función, asumiendo el alcance. $$ phase === '$ digest'?
Spencer Alger
Estoy de acuerdo, a veces $ apply () se usa para activar el resumen, solo llamando al fn por sí mismo ... ¿eso no resultará en un problema?
CMCDragonkai
1
Siento que scope.$apply(fn);debería ser scope.$apply(fn());porque fn () ejecutará la función y no fn. Por favor
ayúdenme a estar
1
@ZenOut La llamada a $ apply admite muchos tipos diferentes de argumentos, incluidas las funciones. Si se pasa una función, evalúa la función.
boxmein
33

Tuve 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:

$timeout(function() {
  // run my code safely here
})

y si dentro de tu código estás usando

esta

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:

.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();

  }]
)
Ciul
fuente
32

Ver http://docs.angularjs.org/error/$rootScope:inprog

El problema surge cuando tiene una llamada $applyque 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 progresserror).

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 $applyse ejecute de forma asincrónica. Esto se puede hacer ejecutando su código dentro de una llamada a $timeoutcon el retraso establecido en 0(que es el valor predeterminado). Sin embargo, llamar a su código adentro $timeoutelimina 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:

... 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 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 $timeoutexceso $apply, no veo por qué no siempre puede usarlo $timeout(con cero retraso) en lugar de hacerlo $apply, ya que hará aproximadamente lo mismo.

Trevor
fuente
Gracias, esto funcionó para mi caso en el que no $applyme llamo a mí mismo pero sigo recibiendo el error.
ariscris
55
La principal diferencia es que $applyes síncrono (su devolución de llamada se ejecuta, luego el código que sigue a $ apply) mientras $timeoutque 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 usando setTimeout. Eso podría generar fallas gráficas si actualizara dos veces el mismo modelo: $timeoutesperará a que la vista se actualice antes de actualizarla nuevamente.
floribon 01 de
Gracias de hecho, trío. Tenía un método llamado como resultado de alguna actividad de $ watch, e intentaba actualizar la interfaz de usuario antes de que mi filtro externo terminara de ejecutarse. Poner eso dentro de una función $ timeout funcionó para mí.
djmarquette
28

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.

dnc253
fuente
heh, pasé todo el día para descubrir que AngularJS simplemente no puede ver los enlaces "mágicamente" y que a veces debería presionarlo con $ apply ().
OZ_
¿Qué significa en absoluto you're not updating the the model correctly? $scope.err_message = 'err message';no es la actualización correcta?
OZ_
2
La única vez que necesita llamar $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 digo you're not updating the the model correctlyque 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.
dnc253
@ dnc253 Estoy de acuerdo y escribí la respuesta. Sabiendo lo que sé ahora, usaría $ timeout (function () {...}); Hace lo mismo que _.defer hace. Ambos difieren al siguiente ciclo de animación.
helado
14

La forma más corta de caja fuerte $applyes:

$timeout(angular.noop)
Brujo
fuente
11

También puede usar evalAsync. ¡Funcionará en algún momento después de que el resumen haya terminado!

scope.evalAsync(function(scope){
    //use the scope...
});
CMCDragonkai
fuente
10

Antes que nada, no lo arregles de esta manera

if ( ! $scope.$$phase) { 
  $scope.$apply(); 
}

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

    $timeout(function(){ 
  // Any code in here will automatically have an $scope.apply() run afterwards 
$scope.myvar = newValue; 
  // And it just works! 
});

Si está usando subrayado o lodash, puede usar defer ():

_.defer(function(){ 
  $scope.$apply(); 
});
Sagar M
fuente
9

A veces, aún obtendrá errores si lo usa de esta manera ( https://stackoverflow.com/a/12859093/801426 ).

Prueba esto:

if(! $rootScope.$root.$$phase) {
...
bullgare
fuente
55
el uso de! $ scope. $$ phase y! $ scope. $ root. $$ phase (no! $ rootScope. $ root. $$ phase) funciona para mí. +1
asprotte
2
$rootScopey anyScope.$rootson el mismo chico $rootScope.$rootes redundante
floribon
5

intenta usar

$scope.applyAsync(function() {
    // your code
});

en vez 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 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:

  $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

  1. AngularJs Docs
Eduardo Eljaiek
fuente
4

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.

$scope.$on('customEventName', function (optionalCustomEventArguments) {
   //TODO: Respond to event
});


$scope.$broadcast('customEventName', optionalCustomEventArguments);
nelsonomuto
fuente
3

yearofmoo hizo un gran trabajo al crear una función reutilizable $ safeApply para nosotros:

https://github.com/yearofmoo/AngularJS-Scope.SafeApply

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);
RNobel
fuente
2

He podido resolver este problema llamando en $evallugar de $applyen lugares donde sé que la $digestfunción se ejecutará.

Según los documentos , $applybásicamente hace esto:

function $apply(expr) {
  try {
    return $eval(expr);
  } catch (e) {
    $exceptionHandler(e);
  } finally {
    $root.$digest();
  }
}

En mi caso, un ng-clickcambia 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 $applycon$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, $evaling es todo lo que necesita hacer.

teleclimber
fuente
2

utilizar $scope.$$phase || $scope.$apply();en su lugar

Visakh B Sujathan
fuente
1

La comprensión de que los documentos angular llaman comprobar el $$phaseun anti-patrón , traté de conseguir $timeouty_.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:

"Haz el resumen a menos que ya esté sucediendo uno"

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 $$phasemétodo de inspección.

SimplGy
fuente
1
¡Me encantaría ver una respuesta informada a esta pregunta!
Ben Wheeler
Es un truco porque significa que pierde el contexto o no entiende el código en este punto: o está dentro del ciclo de digestión angular y no lo necesita, o está asincrónicamente fuera de eso y luego lo necesita. Si no puede saber eso en ese punto del código, entonces no es responsable de digerirlo
floribon
1

Este es mi servicio de utilidades:

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']);
};
ranbuch
fuente
1

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ó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);
  }
}
Ashu
fuente
1

Cuando desactivé el depurador, el error ya no ocurre. En mi caso , fue porque el depurador detuvo la ejecución del código.

jmojico
fuente
0

similar a las respuestas anteriores, pero esto ha funcionado fielmente para mí ... en un servicio agregue:

    //sometimes you need to refresh scope, use this to prevent conflict
    this.applyAsNeeded = function (scope) {
        if (!scope.$$phase) {
            scope.$apply();
        }
    };
Shawn Dotey
fuente
0

Puedes usar

$timeout

para evitar el error

 $timeout(function () {
                        var scope = angular.element($("#myController")).scope();
                        scope.myMethod();
                        scope.$scope();
                    },1);
Satish Singh
fuente
¿Qué pasa si no quiero usar $ timeout?
rahim.nagori
0

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 () {

}); }

Sachin Mishra
fuente
0
        let $timeoutPromise = null;
        $timeout.cancel($timeoutPromise);
        $timeoutPromise = $timeout(() => {
            $scope.$digest();
        }, 0, false);

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

.factory('debounce', [
    '$timeout',
    function ($timeout) {

        return function (func, wait, apply) {
            // apply default is true for $timeout
            if (apply !== false) {
                apply = true;
            }

            var promise;
            return function () {
                var cntx = this,
                    args = arguments;
                $timeout.cancel(promise);
                promise = $timeout(function () {
                    return func.apply(cntx, args);
                }, wait, apply);
                return promise;
            };
        };
    }
])

y el código en sí para escuchar algún evento y llamar a $ digest solo en $ scope que necesita

        let $timeoutPromise = null;
        let $update = debounce(function () {
            $timeout.cancel($timeoutPromise);
            $timeoutPromise = $timeout(() => {
                $scope.$digest();
            }, 0, false);
        }, 0, false);

        let $unwatchModelChanges = $scope.$root.$on('updatePropertiesInspector', function () {
            $update();
        });


        $scope.$on('$destroy', () => {
            $timeout.cancel($update);
            $timeout.cancel($timeoutPromise);
            $unwatchModelChanges();
        });
Sergey Sahakyan
fuente
-3

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:

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;
      }
    ]);
  }
]);
Warren Davis
fuente
-7

Esto resolverá tu problema:

if(!$scope.$$phase) {
  //TODO
}
eebbesen
fuente
No lo haga si (! $ Scope. $$ phase) $ scope. $ Apply (), significa que su $ scope. $ Apply () no es lo suficientemente alto en la pila de llamadas.
MGot90