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?
$timeout()
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.Réponses:
Vous pouvez vérifier si un
$digest
est déjà en cours en vérifiant$scope.$$phase
.$scope.$$phase
reviendra"$digest"
ou"$apply"
si un$digest
ou$apply
est en cours. Je crois que la différence entre ces états est qu'elle$digest
traitera les montres de la portée actuelle et ses enfants, et$apply
traitera les observateurs de toutes les portées.Au point de @ dnc253, si vous vous retrouvez à appeler
$digest
ou$apply
fré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$digest
est 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
la source
if (!$scope.$$phase) $scope.$apply()
", github.com/angular/angular.js/wiki/Anti-PatternsD'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
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é.)
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!
la source
$timeout
plutôt que natifsetTimeout
, pourquoi n'utilisez-vous pas$window
place du natifwindow
?$timeout
dans ce cas est de$timeout
garantir 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.cancel
cela. 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.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:
ou si vous avez du lodash:
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.
la source
underscore.js
. Cette solution ne vaut pas la peine d'importer l'intégralité de la bibliothèque de soulignements uniquement pour utiliser sadefer
fonction. Je préfère de beaucoup la$timeout
solution car tout le monde y a déjà accès$timeout
via angular, sans aucune dépendance sur les autres bibliothèques.De nombreuses réponses ici contiennent de bons conseils mais peuvent également prêter à confusion. La simple utilisation
$timeout
n'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.
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.$digest
si vous savez vraiment que vous n'avez besoin de synchroniser qu'une partie isolée de votre HTML (car une nouvelle$apply
sera 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 )
la source
$timeout
est critiqué au passage. Pouvez-vous donner plus de raisons d'éviter$timeout
?Petite méthode d'aide pratique pour garder ce processus SEC:
la source
scope.$apply(fn);
devrait êtrescope.$apply(fn());
parce que fn () exécutera la fonction et non fn. S'il vous plaît, aidez-moi là où je me trompeJ'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:
et si à l'intérieur de votre code vous utilisez
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:
la source
Voir http://docs.angularjs.org/error/$rootScope:inprog
Le problème se pose lorsque vous avez un appel à
$apply
qui 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 progress
erreur).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
$apply
est exécuté de manière asynchrone. Cela peut être fait en exécutant votre code dans un appel à$timeout
avec le délai défini sur0
(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$digest
cycle, qui, à son tour, fera toutes les mises à jour nécessaires, etc.Solution
Bref, au lieu de faire ça:
faites ceci:
Appelez uniquement
$apply
lorsque 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
$timeout
plus$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.la source
$apply
mais j'obtiens toujours l'erreur.$apply
est synchrone (son rappel est exécuté, puis le code suivant $ apply) alors qu'il$timeout
ne 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'utilisiezsetTimeout
. Cela pourrait entraîner des problèmes graphiques si vous mettiez à jour deux fois le même modèle:$timeout
attendez que la vue soit actualisée avant de la mettre à jour à nouveau.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.la source
you're not updating the the model correctly
?$scope.err_message = 'err message';
n'est pas une mise à jour correcte?$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 correctly
je 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.La forme la plus courte de coffre
$apply
- fort est:la source
Vous pouvez également utiliser evalAsync. Il s'exécutera quelque temps après la fin du résumé!
la source
Tout d'abord, ne le corrigez pas de cette façon
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
Si vous utilisez un trait de soulignement ou un lodash, vous pouvez utiliser defer ():
la source
Parfois, vous obtiendrez toujours des erreurs si vous utilisez cette méthode ( https://stackoverflow.com/a/12859093/801426 ).
Essaye ça:
la source
$rootScope
etanyScope.$root
sont le même gars.$rootScope.$root
est redondant.Vous devez utiliser $ evalAsync ou $ timeout selon le contexte.
Ceci est un lien avec une bonne explication:
la source
essayez d'utiliser
au lieu de
$ 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:
Références:
1. Étendue. $ ApplyAsync () par rapport à Étendue. $ EvalAsync () dans AngularJS 1.3
la source
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.
la source
yearofmoo a fait un excellent travail pour créer une fonction $ safeApply réutilisable pour nous:
Utilisation:
la source
J'ai pu résoudre ce problème en appelant
$eval
plutôt$apply
qu'à des endroits où je sais que la$digest
fonction s'exécutera.Selon les documents ,
$apply
cela fait essentiellement:Dans mon cas, un
ng-click
change 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
$apply
par$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
$eval
vous suffit de faire.la source
utiliser à la
$scope.$$phase || $scope.$apply();
placela source
Comprenant que les documents angulaires appellent la vérification d'
$$phase
un anti-modèle , j'ai essayé d'obtenir$timeout
et_.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:
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
$$phase
méthode d'inspection.la source
Voici mon service utils:
et ceci est un exemple pour son utilisation:
la source
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 fonctionapply(<your scope>)
où vous voulez.la source
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.
la source
similaire aux réponses ci-dessus, mais cela a fonctionné fidèlement pour moi ... dans un service, ajoutez:
la source
Vous pouvez utiliser
pour éviter l'erreur.
la source
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 () {
}); }
la source
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
et le code lui-même pour écouter un événement et appeler $ digest uniquement sur $ scope dont vous avez besoin
la source
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:
la source
Cela résoudra votre problème:
la source