AngularJS: Empêche l'erreur $ digest déjà en cours lors de l'appel de $ scope. $ Apply ()

838

Je constate que j'ai besoin de mettre à jour ma page à ma portée manuellement de plus en plus depuis la construction d'une application en angulaire.

La seule façon que je sache de le faire est d'appeler $apply()depuis la portée de mes contrôleurs et directives. Le problème avec cela est qu'il continue de renvoyer une erreur à la console qui lit:

Erreur: $ digest déjà en cours

Est-ce que quelqu'un sait comment éviter cette erreur ou réaliser la même chose mais d'une manière différente?

Ampoule1
la source
34
C'est vraiment frustrant de devoir utiliser $ appliquer de plus en plus.
OZ_
J'obtiens également cette erreur, même si j'appelle $ apply dans un rappel. J'utilise une bibliothèque tierce pour accéder aux données sur leurs serveurs, donc je ne peux pas profiter de $ http, et je ne veux pas non plus car je devrais réécrire leur bibliothèque pour utiliser $ http.
Trevor
45
utilisation$timeout()
Onur Yıldırım
6
utilisez $ timeout (fn) + 1, cela peut résoudre le problème,! $ scope. $$ phase n'est pas la meilleure solution.
Huei Tan
1
Seulement envelopper code / champ d'appel. $ Applicable à partir dans les délais d' attente (non $ délai d' attente) des fonctions AJAX (pas $ http) et des événements (non ng-*). Assurez-vous, si vous l'appelez depuis une fonction (qui est appelée via timeout / ajax / events), qu'elle ne s'exécute pas également en charge initialement.
Patrick

Réponses:

660

N'utilisez pas ce modèle - Cela finira par provoquer plus d'erreurs qu'il n'en résout. Même si vous pensez que cela a corrigé quelque chose, ce n'est pas le cas.

Vous pouvez vérifier si un $digestest déjà en cours en vérifiant $scope.$$phase.

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

$scope.$$phasereviendra "$digest"ou "$apply"si un $digestou $applyest en cours. Je crois que la différence entre ces états est qu'elle $digesttraitera les montres de la portée actuelle et ses enfants, et $applytraitera les observateurs de toutes les portées.

Au point de @ dnc253, si vous vous retrouvez à appeler $digestou $applyfréquemment, vous pouvez le faire mal. Je trouve généralement que j'ai besoin de digérer quand j'ai besoin de mettre à jour l'état de la portée à la suite d'un événement DOM se déclenchant hors de la portée d'Angular. Par exemple, lorsqu'un modal bootstrap twitter est masqué. Parfois, l'événement DOM se déclenche lorsqu'un $digestest en cours, parfois non. C'est pourquoi j'utilise ce chèque.

J'aimerais connaître une meilleure façon si quelqu'un en connaît une.


Extrait des commentaires: par @anddoutoi

angular.js Anti Patterns

  1. Ne le faites pas if (!$scope.$$phase) $scope.$apply(), cela signifie que vous n'êtes $scope.$apply()pas assez haut dans la pile des appels.
Lee
la source
230
Il me semble que $ digest / $ apply devrait faire cela par défaut
Roy Truelove
21
Notez que dans certains cas, je dois vérifier la portée actuelle ET la portée racine. J'ai obtenu une valeur pour la phase $$ à la racine mais pas sur ma portée. Je pense que cela a quelque chose à voir avec la portée isolée d'une directive, mais ..
Roy Truelove
106
"Arrêtez de faire if (!$scope.$$phase) $scope.$apply()", github.com/angular/angular.js/wiki/Anti-Patterns
anddoutoi
34
@anddoutoi: D'accord; votre lien indique clairement que ce n'est pas la solution; cependant, je ne suis pas sûr de ce que signifie "vous n'êtes pas assez haut dans la pile des appels". Savez-vous ce que cela signifie?
Trevor
13
@threed: voir la réponse de aaronfrost. La bonne façon est d'utiliser le report pour déclencher la digestion dans le cycle suivant. Sinon, l'événement sera perdu et ne mettra pas du tout à jour la portée.
Marek
663

D'après une récente discussion avec les gars angulaires sur ce même sujet: pour des raisons de pérennité, vous ne devez pas utiliser$$phase

Lorsqu'elle est pressée pour la "bonne" façon de le faire, la réponse est actuellement

$timeout(function() {
  // anything you want can go here and will safely be run on the next digest.
})

J'ai récemment rencontré cela lors de l'écriture de services angulaires pour envelopper les API Facebook, Google et Twitter qui, à des degrés divers, ont des rappels remis.

Voici un exemple au sein d'un service. (Par souci de concision, le reste du service - qui a configuré des variables, injecté $ timeout, etc. - a été laissé de côté.)

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);
            }
        });
    });
});

Notez que l'argument de délai pour $ timeout est facultatif et sera par défaut à 0 s'il n'est pas défini ( $ timeout appelle $ browser.defer qui par défaut est 0 si le délai n'est pas défini )

Un peu non intuitif, mais c'est la réponse des gars qui écrivent Angular, donc c'est assez bon pour moi!

betaorbust
la source
5
J'ai rencontré cela plusieurs fois dans mes directives. J'en écrivais un pour le rédacteur et cela s'est avéré fonctionner parfaitement. J'étais à une réunion avec Brad Green et il a dit que Angular 2.0 serait énorme sans cycle de résumé en utilisant la capacité d'observation native de JS et en utilisant un polyfill pour les navigateurs qui n'en avaient pas. À ce stade, nous n'aurons plus besoin de le faire. :)
Michael J.Calkins
Hier, j'ai vu un problème où l'appel de selectize.refreshItems () intérieur de $ timeout a provoqué l'erreur de résumé récursive redoutée. Des idées comment cela pourrait être?
iwein
3
Si vous utilisez $timeoutplutôt que natif setTimeout, pourquoi n'utilisez-vous pas$window place du natif window?
LeeGee
2
@LeeGee: Le point d'utilisation $timeoutdans ce cas est de $timeoutgarantir que la portée angulaire est correctement mise à jour. Si un $ digest n'est pas en cours, il provoquera l'exécution d'un nouveau $ digest.
admiration
2
@webicy Ce n'est pas une chose. Lorsque le corps de la fonction passé à $ timeout est exécuté, la promesse est déjà résolue! Il n'y a absolument aucune raison à cancelcela. De la documentation : "En conséquence, la promesse sera résolue par un rejet." Vous ne pouvez pas résoudre une promesse résolue. Votre annulation ne causera aucune erreur, mais elle ne fera rien de positif non plus.
daemonexmachina
324

Le cycle de résumé est un appel synchrone. Il ne cédera pas le contrôle à la boucle d'événements du navigateur tant qu'il ne sera pas terminé. Il y a plusieurs façons de gérer cela. La façon la plus simple de résoudre ce problème consiste à utiliser le délai d'expiration $ intégré, et une deuxième méthode consiste à utiliser le trait de soulignement ou le lodash (et vous devriez l'être), appelez la commande suivante:

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

ou si vous avez du lodash:

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

Nous avons essayé plusieurs solutions de contournement et nous avons détesté injecter $ rootScope dans tous nos contrôleurs, directives et même dans certaines usines. Ainsi, le délai d'attente $ et _.defer ont été nos préférés jusqu'à présent. Ces méthodes indiquent avec succès à angular d'attendre la prochaine boucle d'animation, ce qui garantira que la portée actuelle. $ Apply est terminée.

glacial
la source
2
Est-ce comparable à l'utilisation de $ timeout (...)? J'ai utilisé $ timeout dans plusieurs cas pour reporter au prochain cycle d'événements et cela semble fonctionner correctement - quelqu'un sait-il s'il y a une raison de ne pas utiliser $ timeout?
Trevor
9
Cela ne devrait vraiment être utilisé que si vous l'utilisez déjà underscore.js. Cette solution ne vaut pas la peine d'importer l'intégralité de la bibliothèque de soulignements uniquement pour utiliser sa deferfonction. Je préfère de beaucoup la $timeoutsolution car tout le monde y a déjà accès $timeoutvia angular, sans aucune dépendance sur les autres bibliothèques.
tennisgent
10
C'est vrai ... mais si vous n'utilisez pas de soulignement ou de lodash ... vous devez réévaluer ce que vous faites. Ces deux bibliothèques ont changé l'apparence de ce code.
2014 glacial
2
Nous avons le lodash comme dépendance pour Restangular (nous allons bientôt éliminer Restangular en faveur de ng-route). Je pense que c'est une bonne réponse, mais ce n'est pas génial de supposer que les gens veulent utiliser le soulignement / lodash. Par tous les moyens ces libs sont très bien ... si vous les utiliser assez ... ces jours - je utiliser des méthodes ES5 qui anéantissent 98% de la raison pour laquelle je l' habitude d' inclure underscore.
BradGreens
2
Vous avez raison @SgtPooki. J'ai modifié la réponse pour inclure l'option d'utiliser également $ timeout. $ timeout et _.defer attendront tous les deux jusqu'à la prochaine boucle d'animation, ce qui garantira que la portée actuelle. $ apply est terminée. Merci de me garder honnête et de m'avoir permis de mettre à jour la réponse ici.
2014 glacial
267

De nombreuses réponses ici contiennent de bons conseils mais peuvent également prêter à confusion. La simple utilisation $timeoutn'est ni la meilleure ni la bonne solution. Assurez-vous également de lire cela si vous êtes préoccupé par les performances ou l'évolutivité.

Ce que vous devez savoir

  • $$phase est privé du cadre et il y a de bonnes raisons à cela.

  • $timeout(callback)attendra jusqu'à ce que le cycle de résumé en cours (le cas échéant) soit terminé, puis exécutera le rappel, puis exécutera à la fin un cycle complet $apply.

  • $timeout(callback, delay, false)fera de même (avec un délai facultatif avant l'exécution du rappel), mais ne déclenchera pas un $apply(troisième argument) qui enregistre les performances si vous n'avez pas modifié votre modèle angulaire ($ scope).

  • $scope.$apply(callback)invoque, entre autres $rootScope.$digest, ce qui signifie qu'il redéfinira la portée racine de l'application et de tous ses enfants, même si vous êtes dans une portée isolée.

  • $scope.$digest()synchronisera simplement son modèle avec la vue, mais ne digèrera pas la portée de ses parents, ce qui peut économiser beaucoup de performances lorsque vous travaillez sur une partie isolée de votre HTML avec une portée isolée (à partir d'une directive principalement). $ digest ne prend pas de rappel: vous exécutez le code, puis digérez.

  • $scope.$evalAsync(callback)a été introduit avec angularjs 1.2, et résoudra probablement la plupart de vos problèmes. Veuillez vous référer au dernier paragraphe pour en savoir plus.

  • si vous obtenez le $digest already in progress error, alors votre architecture est fausse: soit vous n'avez pas besoin de redéfinir votre portée, soit vous ne devriez pas en être responsable (voir ci-dessous).

Comment structurer votre code

Lorsque vous obtenez cette erreur, vous essayez de digérer votre portée pendant qu'elle est déjà en cours: puisque vous ne connaissez pas l'état de votre portée à ce stade, vous n'êtes pas en charge de gérer sa digestion.

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);
});

Et si vous savez ce que vous faites et travaillez sur une petite directive isolée tout en faisant partie d'une grande application angulaire, vous pourriez préférer $ digest au lieu de $ apply pour enregistrer les performances.

Mise à jour depuis Angularjs 1.2

Une nouvelle méthode puissante a été ajoutée à toute portée $: $evalAsync . Fondamentalement, il exécutera son rappel dans le cycle de résumé en cours s'il en a un, sinon un nouveau cycle de résumé commencera à exécuter le rappel.

Ce n'est toujours pas aussi bon que $scope.$digestsi vous savez vraiment que vous n'avez besoin de synchroniser qu'une partie isolée de votre HTML (car une nouvelle $applysera déclenchée si aucune n'est en cours), mais c'est la meilleure solution lorsque vous exécutez une fonction dont vous ne pouvez pas savoir s'il sera exécuté de manière synchrone ou non , par exemple après avoir récupéré une ressource potentiellement mise en cache: cela nécessitera parfois un appel asynchrone à un serveur, sinon la ressource sera récupérée localement de manière synchrone.

Dans ces cas et tous les autres où vous en avez eu !$scope.$$phase, assurez-vous d'utiliser$scope.$evalAsync( callback )

floribon
la source
4
$timeoutest critiqué au passage. Pouvez-vous donner plus de raisons d'éviter $timeout?
mlhDev
88

Petite méthode d'aide pratique pour garder ce processus SEC:

function safeApply(scope, fn) {
    (scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
}
lambinator
la source
6
Votre application sûre m'a aidé à comprendre ce qui se passait beaucoup plus qu'autre chose. Merci d'avoir posté ça.
Jason More
4
J'étais sur le point de faire la même chose, mais cela ne signifie-t-il pas que les modifications que nous apportons dans fn () ne seront pas vues par $ digest? Ne serait-il pas préférable de retarder la fonction, en supposant la portée. $$ phase === '$ digest'?
Spencer Alger
Je suis d'accord, parfois $ apply () est utilisé pour déclencher le résumé, en appelant simplement le fn par lui-même ... cela ne causera-t-il pas un problème?
CMCDragonkai
1
Je pense que ça scope.$apply(fn);devrait être scope.$apply(fn());parce que fn () exécutera la fonction et non fn. S'il vous plaît, aidez-moi là où je me trompe
madhu131313
1
@ZenOut L'appel à $ apply prend en charge différents types d'arguments, y compris les fonctions. Si passé une fonction, il évalue la fonction.
boxmein
33

J'ai eu le même problème avec des scripts tiers comme CodeMirror par exemple et Krpano, et même en utilisant les méthodes safeApply mentionnées ici, je n'ai pas résolu l'erreur pour moi.

Mais qu'est-ce qui l'a résolu, c'est d'utiliser le service $ timeout (n'oubliez pas de l'injecter d'abord).

Ainsi, quelque chose comme:

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

et si à l'intérieur de votre code vous utilisez

cette

peut-être parce que c'est à l'intérieur du contrôleur d'une directive d'usine ou que vous avez juste besoin d'une sorte de liaison, alors vous feriez quelque chose comme:

.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
la source
32

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

Le problème se pose lorsque vous avez un appel à $applyqui est parfois exécuté de manière asynchrone en dehors du code angulaire (lorsque $ apply doit être utilisé) et parfois de manière synchrone dans le code angulaire (ce qui provoque l' $digest already in progresserreur).

Cela peut se produire, par exemple, lorsque vous disposez d'une bibliothèque qui récupère de manière asynchrone des éléments d'un serveur et les met en cache. La première fois qu'un élément est demandé, il sera récupéré de manière asynchrone afin de ne pas bloquer l'exécution du code. La deuxième fois, cependant, l'élément est déjà dans le cache afin qu'il puisse être récupéré de manière synchrone.

Pour éviter cette erreur, vous devez vous assurer que le code qui appelle $applyest exécuté de manière asynchrone. Cela peut être fait en exécutant votre code dans un appel à $timeoutavec le délai défini sur 0(qui est la valeur par défaut). Cependant, appeler votre code à l'intérieur $timeoutélimine la nécessité d'appeler $apply, car $ timeout déclenchera lui-même un autre $digestcycle, qui, à son tour, fera toutes les mises à jour nécessaires, etc.

Solution

Bref, au lieu de faire ça:

... your controller code...

$http.get('some/url', function(data){
    $scope.$apply(function(){
        $scope.mydate = data.mydata;
    });
});

... more of your controller code...

faites ceci:

... your controller code...

$http.get('some/url', function(data){
    $timeout(function(){
        $scope.mydate = data.mydata;
    });
});

... more of your controller code...

Appelez uniquement $applylorsque vous connaissez le code en cours d'exécution, il sera toujours exécuté en dehors du code angulaire (par exemple, votre appel à $ apply se fera à l'intérieur d'un rappel appelé par du code en dehors de votre code angulaire).

À moins que quelqu'un ne soit conscient de certains inconvénients importants à utiliser $timeoutplus $apply, je ne vois pas pourquoi vous ne pourriez pas toujours utiliser $timeout(avec zéro retard) au lieu de $apply, car cela fera à peu près la même chose.

Trevor
la source
Merci, cela a fonctionné pour mon cas où je ne m'appelle pas $applymais j'obtiens toujours l'erreur.
ariscris
5
La principale différence est qu'il $applyest synchrone (son rappel est exécuté, puis le code suivant $ apply) alors qu'il $timeoutne l'est pas: le code actuel après le timeout est exécuté, puis une nouvelle pile commence par son rappel, comme si vous l'utilisiez setTimeout. Cela pourrait entraîner des problèmes graphiques si vous mettiez à jour deux fois le même modèle: $timeoutattendez que la vue soit actualisée avant de la mettre à jour à nouveau.
floribon
Merci en effet, trois. J'ai eu une méthode appelée à la suite d'une activité $ watch et j'essayais de mettre à jour l'interface utilisateur avant que mon filtre externe ait fini de s'exécuter. Mettre cela dans une fonction $ timeout a fonctionné pour moi.
djmarquette
28

Lorsque vous obtenez cette erreur, cela signifie essentiellement qu'elle est déjà en train de mettre à jour votre vue. Vous ne devriez vraiment pas avoir besoin d'appeler $apply()depuis votre contrôleur. Si votre vue ne se met pas à jour comme prévu, et que vous obtenez cette erreur après avoir appelé $apply(), cela signifie très probablement que vous ne mettez pas à jour le modèle correctement. Si vous publiez des détails, nous pourrions trouver le problème principal.

dnc253
la source
heh, j'ai passé toute la journée à découvrir que AngularJS ne peut tout simplement pas regarder les liaisons "par magie" et je devrais parfois le pousser avec $ apply ().
OZ_
qu'est-ce que cela signifie you're not updating the the model correctly? $scope.err_message = 'err message';n'est pas une mise à jour correcte?
OZ_
2
Le seul moment où vous devez appeler $apply()est lorsque vous mettez à jour le modèle "en dehors" d'angular (par exemple à partir d'un plugin jQuery). Il est facile de tomber dans le piège de la vue qui ne semble pas droite, et vous jetez donc un tas de $apply()s partout, ce qui se termine alors avec l'erreur vue dans l'OP. Quand je dis, you're not updating the the model correctlyje veux simplement dire que toute la logique métier ne remplit pas correctement tout ce qui pourrait être dans la portée, ce qui conduit à ce que la vue ne soit pas comme prévu.
dnc253
@ dnc253 Je suis d'accord et j'ai écrit la réponse. Sachant ce que je sais maintenant, j'utiliserais $ timeout (function () {...}); Cela fait la même chose que _.defer. Ils reportent tous les deux à la prochaine boucle d'animation.
2014 glacial
14

La forme la plus courte de coffre $apply- fort est:

$timeout(angular.noop)
démoniste
la source
11

Vous pouvez également utiliser evalAsync. Il s'exécutera quelque temps après la fin du résumé!

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

Tout d'abord, ne le corrigez pas de cette façon

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

Cela n'a pas de sens car $ phase n'est qu'un indicateur booléen pour le cycle $ digest, donc votre $ apply () ne s'exécute parfois pas. Et rappelez-vous que c'est une mauvaise pratique.

Utilisez plutôt $timeout

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

Si vous utilisez un trait de soulignement ou un lodash, vous pouvez utiliser defer ():

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

Parfois, vous obtiendrez toujours des erreurs si vous utilisez cette méthode ( https://stackoverflow.com/a/12859093/801426 ).

Essaye ça:

if(! $rootScope.$root.$$phase) {
...
bullgare
la source
5
utiliser à la fois! $ scope. $$ phase et! $ scope. $ root. $$ phase (pas! $ rootScope. $ root. $$ phase) fonctionne pour moi. +1
asprotte
2
$rootScopeet anyScope.$rootsont le même gars. $rootScope.$rootest redondant.
floribon
5

essayez d'utiliser

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

au lieu de

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

$ applyAsync Planifiez l'invocation de $ apply pour qu'elle se produise ultérieurement. Cela peut être utilisé pour mettre en file d'attente plusieurs expressions qui doivent être évaluées dans le même résumé.

REMARQUE: Dans le $ digest, $ applyAsync () ne videra que si la portée actuelle est $ rootScope. Cela signifie que si vous appelez $ digest sur une portée enfant, il ne videra pas implicitement la file d'attente $ applyAsync ().

Exmaple:

  $scope.$applyAsync(function () {
                if (!authService.authenticated) {
                    return;
                }

                if (vm.file !== null) {
                    loadService.setState(SignWizardStates.SIGN);
                } else {
                    loadService.setState(SignWizardStates.UPLOAD_FILE);
                }
            });

Références:

1. Étendue. $ ApplyAsync () par rapport à Étendue. $ EvalAsync () dans AngularJS 1.3

  1. AngularJs Docs
Eduardo Eljaiek
la source
4

Je vous conseille d'utiliser un événement personnalisé plutôt que de déclencher un cycle de résumé.

J'en suis venu à la conclusion que la diffusion d'événements personnalisés et l'inscription d'écouteurs pour ces événements est une bonne solution pour déclencher une action que vous souhaitez effectuer, que vous soyez ou non dans un cycle de résumé.

En créant un événement personnalisé, vous êtes également plus efficace avec votre code car vous ne déclenchez que des écouteurs abonnés audit événement et NE déclenchez PAS toutes les surveillances liées à la portée comme vous le feriez si vous appeliez la portée. $ Apply.

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


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

yearofmoo a fait un excellent travail pour créer une fonction $ safeApply réutilisable pour nous:

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

Utilisation:

//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
la source
2

J'ai pu résoudre ce problème en appelant $evalplutôt $applyqu'à des endroits où je sais que la $digestfonction s'exécutera.

Selon les documents , $applycela fait essentiellement:

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

Dans mon cas, un ng-clickchange une variable dans une portée, et un $ watch sur cette variable change d'autres variables qui doivent l'être $applied. Cette dernière étape provoque l'erreur "digest déjà en cours".

En remplaçant $applypar$eval à l'intérieur de l'expression de surveillance, les variables de portée sont mises à jour comme prévu.

Par conséquent, il semble que si le résumé doit être exécuté de toute façon en raison d'un autre changement dans Angular, il $evalvous suffit de faire.

teleclimber
la source
2

utiliser à la $scope.$$phase || $scope.$apply();place

Visakh B Sujathan
la source
1

Comprenant que les documents angulaires appellent la vérification d' $$phaseun anti-modèle , j'ai essayé d'obtenir $timeoutet_.defer au travail.

Le délai d'attente et les méthodes différées créent un flash de {{myVar}}contenu non analysé dans le dom comme un FOUT . Pour moi, ce n'était pas acceptable. Cela me laisse sans grand chose à dire dogmatiquement que quelque chose est un hack, et qu'il n'y a pas d'alternative appropriée.

La seule chose qui fonctionne à chaque fois est:

if(scope.$$phase !== '$digest'){ scope.$digest() }.

Je ne comprends pas le danger de cette méthode, ni pourquoi elle est décrite comme un hack par les gens dans les commentaires et l'équipe angulaire. La commande semble précise et facile à lire:

"Faites le résumé à moins qu'il y en ait déjà un"

Dans CoffeeScript, c'est encore plus joli:

scope.$digest() unless scope.$$phase is '$digest'

Quel est le problème avec ça? Existe-t-il une alternative qui ne crée pas de FOUT? $ safeApply semble correct, mais utilise également la $$phaseméthode d'inspection.

SimplGy
la source
1
J'aimerais voir une réponse éclairée à cette question!
Ben Wheeler
C'est un hack car cela signifie que vous manquez de contexte ou ne comprenez pas le code à ce stade: soit vous êtes dans un cycle de résumé angulaire et vous n'en avez pas besoin, soit vous êtes en dehors de cela de manière asynchrone et vous en avez besoin. Si vous ne pouvez pas savoir cela à ce point du code, vous n'êtes pas responsable de le digérer
floribon
1

Voici mon service utils:

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);
        }
    };
});

et ceci est un exemple pour son utilisation:

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
la source
1

J'utilise cette méthode et elle semble fonctionner parfaitement bien. Cela attend juste la fin du cycle, puis se déclenche apply(). Appelez simplement la fonction apply(<your scope>)où vous voulez.

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
la source
1

Lorsque j'ai désactivé le débogueur, l'erreur ne se produit plus. Dans mon cas , c'était à cause du débogueur qui arrêtait l'exécution du code.

jmojico
la source
0

similaire aux réponses ci-dessus, mais cela a fonctionné fidèlement pour moi ... dans un service, ajoutez:

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

Vous pouvez utiliser

$timeout

pour éviter l'erreur.

 $timeout(function () {
                        var scope = angular.element($("#myController")).scope();
                        scope.myMethod();
                        scope.$scope();
                    },1);
Satish Singh
la source
Et si je ne veux pas utiliser $ timeout
rahim.nagori
0

Le problème survient essentiellement lorsque nous demandons à angular d'exécuter le cycle de résumé, même si son processus est en cours, ce qui crée un problème angulaire pour la compréhension. exception de conséquence dans la console.
1. Il n'a aucun sens d'appeler scope. $ Apply () à l'intérieur de la fonction $ timeout car en interne, il fait de même.
2. Le code va de pair avec la fonction JavaScript vanilla car sa définition angulaire non angulaire native, c'est-à-dire setTimeout
3. Pour ce faire, vous pouvez utiliser

if (! Scope. $$ phase) {
scope. $ EvalAsync (function () {

}); }

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

Voici une bonne solution pour éviter cette erreur et éviter $ apply

vous pouvez combiner cela avec debounce (0) si vous appelez en fonction d'un événement externe. Ci-dessus est le «debounce» que nous utilisons, et un exemple complet de code

.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;
            };
        };
    }
])

et le code lui-même pour écouter un événement et appeler $ digest uniquement sur $ scope dont vous avez besoin

        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
la source
-3

Trouvé ceci: https://coderwall.com/p/ngisma où Nathan Walker (près du bas de la page) suggère un décorateur dans $ rootScope pour créer func 'safeApply', code:

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
la source
-7

Cela résoudra votre problème:

if(!$scope.$$phase) {
  //TODO
}
eebbesen
la source
Ne le faites pas si (! $ Scope. $$ phase) $ scope. $ Apply (), cela signifie que votre $ scope. $ Apply () n'est pas assez haut dans la pile des appels.
MGot90