AngularJS : Éviter les erreurs de $digest déjà en cours lors de l'appel de $scope.$appliquer()
Je suis la recherche que j'ai besoin de mettre à jour ma page à ma portée manuellement de plus en plus et depuis la construction d'une application angulaire.
Le seul moyen que je connaisse pour ce faire est d'appeler $apply()
de la portée de mes contrôleurs et des directives. Le problème, c'est qu'il garde de lancer une erreur de la console qui lit :
Erreur: $digest déjà en cours
Personne ne sait comment faire pour éviter cette erreur ou de réaliser la même chose mais d'une manière différente?
- C'est vraiment frustrant chose que nous avons besoin d'utiliser $s'appliquent de plus en plus.
- J'obtiens cette erreur ainsi, même si je suis appelant $s'appliquent dans un rappel. Je suis à l'aide d'un tiers de la bibliothèque pour accéder à des données sur leurs serveurs, donc je ne peux pas profiter de $http, je ne veux depuis que j'aurais à réécrire leur bibliothèque à utiliser $http.
- utilisation
$timeout()
- utiliser $timeout(fn) + 1, Il peut résoudre le problème, !$la portée.$$la phase n'est pas la meilleure solution.
- Seulement enrouler code/appel de la portée.$s'appliquent à partir de dans délais d'attente (pas de $timeout) fonctions AJAX (pas $http) et des événements (pas
ng-*
). S'assurer, si vous appelez à partir de l'intérieur d'une fonction (qui est appelé via le timeout/ajax/événements), qu'il n'est pas aussi en cours d'exécution sur la charge initialement. - J'obtiens cette erreur dans un endroit où je ne suis même pas appeler $s'appliquent. Je vais appeler
element[0].focus();
- Aussi, essayez d'utiliser $digest si vous le pouvez, car il ne fonctionne que dans le champ d'application et non sur la totalité de l'app. Pour des vitesses de souci! 🙂
- angularjs et $s'appliquent
Vous devez vous connecter pour publier un commentaire.
Vous pouvez vérifier si un
$digest
est déjà en cours en cochant$scope.$$phase
.$scope.$$phase
sera de retour"$digest"
ou"$apply"
si un$digest
ou$apply
est en cours. Je crois que la différence entre ces états est que$digest
traitera les montres de la portée actuelle et de ses enfants, et$apply
traitera les observateurs de toutes les portées.À @dnc253 point, si vous vous trouvez en appelant
$digest
ou$apply
fréquemment, vous pouvez peut-être faire le mal. Je trouve en général que j'ai besoin de digérer quand j'ai besoin de mettre à jour le champ d'application de l'état en tant que résultat d'un DOM événement de tir hors de la portée Angulaire. Par exemple, lorsqu'un twitter bootstrap modal devient cachée. Parfois, les DOM événement se déclenche lorsqu'une$digest
est en cours, parfois pas. C'est pourquoi j'utilise cette case.J'aimerais savoir une meilleure façon, si quelqu'un connaît un.
De commentaires:
par @anddoutoi
angular.js Anti Motifs
if (!$scope.$$phase) $scope.$apply()
", github.com/angular/angular.js/wiki/Anti-Patterns$$
méthodesevalAsync
pas faire ce que vous voulez?À partir d'une récente discussion avec l'angle de gars à ce sujet: Pour l'épreuve de l'avenir raisons, vous ne devez pas utiliser
$$phase
Lorsqu'il est pressé pour le "droit" de le faire, la réponse est actuellement
J'ai récemment rencontré ce lors de l'écriture angulaire des services à envelopper les facebook, google et twitter Api qui, à des degrés divers, sont des rappels remis.
Voici un exemple dans un service. (Par souci de concision, le reste du service -- que définir des variables, injecté $timeout etc. -- a été laissé.)
Noter que l'argument des délais pour $timeout est optionnel et sera par défaut la valeur 0 si la gauche unset ($timeout appels $navigateur.reporter qui par défaut à 0 si le retard n'est pas défini)
Un peu de non-intuitif, mais c'est la réponse de la part des gars de l'écriture Anguleuse, de sorte qu'il est assez bon pour moi!
$timeout
plutôt que de natifssetTimeout
, pourquoi ne pas utiliser$window
au lieu de la maternellewindow
?$timeout
dans ce cas, c'est que$timeout
assure que l'angle de champ est mis à jour correctement. Si un $digest n'est pas en cours, il va provoquer une nouvelle $digest à exécuter.var myTimer =$timeout(function(){/*code to execute here*/ $timeout.cancel(myTimer); })
cancel
il. À partir de la docs: "en conséquence de cela, la promesse sera résolu avec un rejet." Vous ne pouvez pas résoudre un résolus promesse. Votre annulation ne cause pas des erreurs, mais il ne sera pas faire quelque chose de positif non plus.Le digérer cycle est un appel synchrone. Ne pas céder le contrôle du navigateur d'événements en boucle jusqu'à ce qu'il se fait. Il existe quelques moyens pour y remédier. La meilleure façon de traiter cette question est d'utiliser le construit en $timeout, et une seconde manière, si vous utilisez le trait de soulignement ou lodash (et vous devriez), appelez le suivant:
ou si vous avez lodash:
Nous avons essayé plusieurs solutions, et nous avons détesté l'injection de $rootScope dans l'ensemble de nos contrôleurs, des directives, et même de certaines usines. Ainsi, l' $timeout "et"_".reporter ont été notre préféré à ce jour. Ces méthodes avec succès le dire angulaire d'attendre jusqu'à la prochaine animation en boucle, ce qui permettra de garantir que le champ d'application actuel.$appliquer plus de.
underscore.js
. Cette solution n'est pas la peine de l'importation de l'ensemble de souligner la bibliothèque juste à utiliser sesdefer
fonction. Je préfère de beaucoup le$timeout
solution parce que tout le monde a déjà accès à$timeout
par angulaire, sans dépendances sur d'autres bibliothèques.if (!$rootScope.$$phase)
avec_.defer()
dans une directive, mais cela ne semble pas fonctionner dans mon cas. La directive fait une transition css, par le biais dejQuery.animate()
et écrit la largeur et la hauteur en arrière dans le périmètre au cours de l'animation étapes. Avecscope.$$phase
j'ai toujours une largeur et hauteur de la valeur dans le champ d'application (un graphique directive exige que ces valeurs et renvoie une erreur, si elles ne sont pas définies), avec_.defer()
ils semblent être trop tard. Je n'ai pas analysé plus profondes, mais dans ma situation$rootScope.$$phase
œuvres, alors que_.defer()
ne l'est pas._.defer($scope.$apply);
. Franchement, je ne sais pas encore pourquoi pas... :-/$apply
dans le cadre de la fenêtre, pas de la$scope
. Cela aurait fonctionné:_.defer($scope.$apply.bind($scope))
, cause vous êtes la liaison le contexte de$apply
être$scope
.$timeout
sinon il va donner d'erreur.stackoverflow.com/a/19009389Beaucoup de réponses ici, contiennent de bons conseils, mais peut aussi conduire à la confusion. Simplement à l'aide de
$timeout
est pas la meilleure, ni la bonne solution.Aussi, assurez-vous de lire que si vous êtes préoccupé par les performances ou l'évolutivité.
Choses que vous devez savoir
$$phase
est privé pour le cadre et il y a de bonnes raisons pour cela.$timeout(callback)
va attendre jusqu'à ce que le résumé actuel cycle (le cas échéant) est fait, puis exécuter la fonction de rappel, puis exécuter à la fin une pleine$apply
.$timeout(callback, delay, false)
va faire la même chose (avec une option de délai avant l'exécution de la fonction de rappel), mais ne sera pas le feu à un$apply
(troisième argument) qui enregistre des performances si vous n'avez pas modifié votre Angulaire du modèle ($champ).$scope.$apply(callback)
invoque, entre autres choses,$rootScope.$digest
, ce qui signifie qu'il sera redigest la racine de la portée de la demande et de tous ses enfants, même si vous êtes dans un cas isolé portée.$scope.$digest()
sera tout simplement synchroniser son modèle à la vue, mais ne sera pas digérer ses parents portée, ce qui permet d'économiser beaucoup de performances lorsque vous travaillez sur une partie isolée de votre code HTML avec un isolé champ d'application (à partir d'une directive pour la plupart). $digest ne prenez pas un rappel: vous exécutez le code, puis digérer.$scope.$evalAsync(callback)
a été introduit avec angularjs 1.2, et sera probablement résoudre la plupart de vos problèmes. Veuillez consulter le dernier paragraphe pour en savoir plus à ce sujet.si vous obtenez le
$digest already in progress error
, alors votre architecture est faux: soit vous n'avez pas besoin de redigest votre portée, ou vous ne devriez pas être en charge de ce (voir ci-dessous).Comment structurer votre code
Quand vous obtenez une erreur, vous êtes en train de digérer votre portée alors qu'il est déjà en cours: car vous ne savez pas l'état de votre portée à ce point, vous n'êtes pas en charge de la relation avec sa digestion.
Et si vous savez ce que vous faites et de travailler sur un isolé petites directive, alors qu'une partie d'un grand Angulaire de l'application, vous pourriez préférer $digest au lieu de plus de $sur appliquer pour enregistrer les spectacles.
Mise à jour depuis Angularjs 1.2
Un nouvel outil de méthode a été ajoutée à tout $portée:
$evalAsync
. Fondamentalement, il exécutera son rappel dans le résumé actuel cycle si l'on est en cours, sinon, un nouveau digérer cycle démarre l'exécution de la fonction de rappel.Qui n'est toujours pas aussi bon qu'un
$scope.$digest
si vous savez vraiment que vous avez seulement besoin de synchroniser une partie isolée de votre code HTML (depuis un nouveau$apply
sera déclenchée si aucun n'est en cours), mais c'est la meilleure solution lorsque vous exécutez une fonction qui vous ne pouvez pas savoir si elle sera exécutée de façon synchrone ou non, par exemple, après l'extraction d'une ressource potentiellement mis en cache: parfois, cela nécessitera un appel asynchrone à un serveur, sinon la ressource localement récupéré de manière synchrone.Dans ces cas, et tous les autres où vous avez eu une
!$scope.$$phase
, assurez-vous d'utiliser$scope.$evalAsync( callback )
$timeout
est critiqué en passant. Pouvez-vous donner plus de raisons d'éviter$timeout
?Peu maniable méthode d'aide à garder le processus SEC:
scope.$apply(fn);
devrait êtrescope.$apply(fn());
parce que fn() exécute la fonction et pas fn. S'il vous plaît aidez-moi à l'endroit où je me trompeJ'ai eu le même problème avec des tiers, des scripts comme CodeMirror, par exemple, et Krpano,
et même en utilisant safeApply méthodes mentionnées ici n'ont pas résolu l'erreur pour moi.
Mais ce qui ne l'a résolu est à l'aide de $timeout service (ne pas oublier de se l'injecter en premier).
Donc, quelque chose comme:
et si à l'intérieur de votre code que vous utilisez
peut-être parce que c'est à l'intérieur d'une usine de directive du contrôleur ou tout simplement besoin d'un certain type de liaison, alors vous devez faire quelque chose comme:
Voir http://docs.angularjs.org/error/$rootScope:inprog
Le problème se pose lorsque vous avez un appel à
$apply
qui est parfois exécuter de manière asynchrone à l'extérieur du Angulaire de code (quand $s'appliquent doivent être utilisés) et parfois de façon synchrone à l'intérieur Angulaire de code (ce qui provoque la$digest already in progress
erreur).Cela peut arriver, par exemple, lorsque vous avez une bibliothèque de façon asynchrone extrait des éléments à partir d'un serveur et les caches. La première fois qu'un élément est demandée, elle est récupérée de manière asynchrone afin de ne pas bloquer l'exécution de code. La deuxième fois, cependant, l'article est déjà dans le cache de sorte qu'il peut être récupéré de manière synchrone.
La façon d'éviter cette erreur est de s'assurer que le code qui appelle
$apply
est exécuté de manière asynchrone. Cela peut être fait par l'exécution de votre code à l'intérieur d'un appel à$timeout
avec le délai de0
(qui est la valeur par défaut). Toutefois, l'appel de votre code à l'intérieur de$timeout
supprime la nécessité d'appeler$apply
, parce que $timeout sera le déclencheur d'une autre$digest
cycle sur son propre, qui, à leur tour, faire toutes les mises à jour nécessaires, etc.Solution
En bref, au lieu de faire ceci:
ce faire:
Seulement appel
$apply
quand vous savez que le code marche, il va toujours être exécuté en dehors de l'Angulaire de code (par exemple, votre appel à $s'appliquent va se passer à l'intérieur d'un callback qui est appelée par le code de l'extérieur de votre Angulaire de code).À moins que quelqu'un est au courant de certains percutants inconvénient de l'utilisation des
$timeout
sur$apply
, je ne vois pas pourquoi vous ne pouvez pas toujours utiliser$timeout
(avec retard zéro) au lieu de$apply
, comme il le fera approximativement la même chose.$apply
moi-même, mais encore obtenir l'erreur.$apply
est synchrone (son rappel est exécutée, puis le code suivant $s'appliquent), tandis que$timeout
n'est pas: le code actuel de délai suivantes est exécutée, puis une nouvelle pile commence avec son rappel, comme si vous étiez à l'aide desetTimeout
. Qui pourrait conduire à des problèmes graphiques si vous ont été mise à jour deux fois le même modèle:$timeout
va attendre la vue pour vous rafraîchir avant de mettre à jour à nouveau.Lorsque vous obtenez cette erreur, cela signifie qu'il est déjà dans le processus de mise à jour de votre point de vue. Vous ne devriez vraiment pas besoin d'appeler
$apply()
au sein de votre contrôleur. Si votre vue n'est pas mise à jour que vous pouvez vous attendre, et puis, vous obtenez cette erreur après l'appel de$apply()
, cela signifie très probablement que vous n'êtes pas la mise à jour de la le modèle correctement. Si vous poster quelques détails, nous avons pu comprendre le cœur du problème.you're not updating the the model correctly
?$scope.err_message = 'err message';
n'est pas de mise à jour correct?$apply()
est lorsque vous mettez à jour le modèle de "l'extérieur" du angulaire(par exemple à partir d'un plugin jQuery). Il est facile de tomber dans le piège de la vue ne cherche pas à droite, et ainsi de vous jeter un tas de$apply()
s partout, les extrémités puis avec l'erreur vu dans l'OP. Quand je disyou're not updating the the model correctly
je viens de dire toute la logique métier pas correctement le remplissage de tout ce qui pourrait être dans le champ d'application, ce qui conduit à la vue ne regarde pas comme prévu.La forme la plus courte de sécurité
$apply
est:Vous pouvez également utiliser evalAsync. Il sera exécuté peu de temps après digest a fini!
Tout d'abord, ne pas fixer de cette façon
Il n'a pas de sens parce que $phase est juste un indicateur booléen pour l' $digest cycle, de sorte que votre $apply() parfois ne fonctionne pas. Et n'oubliez pas que c'est une mauvaise pratique.
Au lieu de cela, utiliser
$timeout
Si vous utilisez le trait de soulignement ou lodash, vous pouvez utiliser reporter():
Parfois, vous aurez toujours des erreurs si vous utilisez cette manière (https://stackoverflow.com/a/12859093/801426).
Essayez ceci:
$rootScope
etanyScope.$root
sont les mêmes gars.$rootScope.$root
est redondante.Vous devez utiliser $evalAsync ou $timeout selon le contexte.
C'est un lien avec une bonne explication:
Je vous conseille d'utiliser un événement personnalisé plutôt que de déclencher une digérer cycle.
Je viens de trouver que la radiodiffusion des événements personnalisés et enregistrer les écouteurs pour cet événement est une bonne solution pour le déclenchement d'une action que vous souhaitez se produire si oui ou non vous êtes dans un condensé de cycle.
Par la création d'un événement personnalisé vous sont également d'être plus efficace avec votre code, parce que vous êtes seulement de déclenchement des auditeurs souscrit à l'événement et de ne PAS déclencher toutes les montres liée à la portée, comme vous le feriez si vous avez appelé la portée.$s'appliquent.
essayez d'utiliser
au lieu de
$applyAsync Calendrier de l'invocation de $s'appliquent à se produire à une date ultérieure. Ceci peut être utilisé à la file d'attente de plusieurs expressions qui doivent être évalués dans la même empreinte.
REMARQUE: Dans le $digest, $applyAsync() ne flush, si le champ d'application actuel est de $rootScope. Cela signifie que si vous appelez $condensé sur un enfant, il ne sera pas implicitement rincer l' $applyAsync() de la file d'attente.
Exemple:
Références:
1.La portée.$applyAsync() vs Portée.$evalAsync() dans AngularJS 1.3
yearofmoo fait un excellent travail à la création d'une réutilisables $safeApply pour nous :
Utilisation :
utilisation
$scope.$$phase || $scope.$apply();
au lieuComprendre que l'angle de documents d'appel vérification de la
$$phase
un anti-modèle, j'ai essayé d'obtenir$timeout
et_.defer
de travail.Le délai d'attente et différés méthodes permettent de créer un flash de unparsed
{{myVar}}
contenu dans les dom comme un FOUT. Pour moi, ce n'était pas acceptable. Il me laisse sans trop être dit de façon dogmatique que quelque chose est un hack, et ne pas avoir une alternative adapté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, ou pourquoi il est décrit comme un hack de gens dans les commentaires et l'angulaire de l'équipe. La commande semble précis et facile à lire:
En CoffeeScript, c'est encore plus joli:
scope.$digest() unless scope.$$phase is '$digest'
Quel est le problème avec ça? Est-il une alternative qui ne crée pas de FOUT? $safeApply semble bien, mais utilise le
$$phase
méthode d'inspection, trop.C'est mon utils services:
et ceci est un exemple de son utilisation:
J'ai été en utilisant cette méthode et il semble fonctionner parfaitement bien. Cette juste attend pour le moment, le cycle est terminé et puis déclenche
apply()
. Il suffit d'appeler la fonctionapply(<your scope>)
à partir de n'importe où vous voulez.Lorsque j'ai désactivé le débogueur , l'erreur ne se produit pas plus. Dans mon cas, c'était à cause de débogueur l'arrêt de l'exécution de code.
semblables à des réponses ci-dessus, mais cela a fonctionné fidèlement pour moi...
dans un service ajouter:
Vous pouvez utiliser
pour éviter l'erreur.
La question est fondamentalement à venir quand, nous demandons angulaire pour exécuter le digest du cycle, même si son processus de création problème angulaire de la compréhension. conséquence exception dans la console.
1. Il n'a pas de sens d'appeler portée.$appliquer() à l'intérieur de l' $timeout, car intérieurement il en fait de même.
2. Le code va avec la vanille fonction JavaScript en raison de ses natif de ne pas angulaire définie à savoir setTimeout
3. Pour ce faire vous pouvez utiliser
si(!la portée.$$phase){
la portée.$evalAsync(function(){
});
}
Trouvé ceci: https://coderwall.com/p/ngisma où Nathan Walker (près de bas de page) suggère un décorateur $rootScope pour créer func 'safeApply', code:
Ce sera de résoudre votre problème: