Bon nombre des réponses données ici contiennent de bons conseils mais peuvent également prêter à confusion. Il suffit d'utiliser $timeout
es no la meilleure ni la bonne solution. N'oubliez pas non plus de lire cela si vous êtes préoccupé par les performances ou l'évolutivité.
Ce que vous devez savoir
-
$$phase
est privée au cadre et il y a de bonnes raisons pour cela.
-
$timeout(callback)
attendra jusqu'à ce que le cycle de digestion actuel (s'il y en a un) soit terminé, puis exécutera la fonction de rappel et, à la fin, un cycle de digestion complet. $apply
.
-
$timeout(callback, delay, false)
fera la même chose (avec un délai optionnel avant l'exécution de la fonction de rappel), mais ne déclenchera pas d'action de rappel. $apply
(troisième argument) qui sauve les performances si vous n'avez pas modifié votre modèle Angular ($scope).
-
$scope.$apply(callback)
invoque, entre autres choses, $rootScope.$digest
ce qui signifie qu'il redigestera l'étendue de la racine de l'application et tous ses enfants, même si vous vous trouvez dans une étendue 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 callback : vous exécutez le code, puis vous 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 à son sujet.
-
si vous obtenez le $digest already in progress error
alors votre architecture est erronée : soit vous n'avez pas besoin de redigester votre champ d'application, soit tu ne devrais pas être en charge de ça (voir ci-dessous).
Comment structurer votre code
Lorsque vous obtenez cette erreur, vous essayez de digérer votre portée alors qu'elle est déjà en cours : puisque vous ne connaissez pas l'état de votre portée à ce moment-là, vous n'êtes pas en charge de vous occuper de 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 que vous travaillez sur une petite directive isolée dans le cadre d'une grande application Angular, vous pouvez préférer $digest à $apply pour économiser des performances.
Mise à jour depuis Angularjs 1.2
Une nouvelle méthode puissante a été ajoutée à tout $scope : $evalAsync
. Fondamentalement, il exécutera son rappel dans le cycle de digestion actuel s'il y en a un, sinon un nouveau cycle de digestion commencera à exécuter le rappel.
Ce n'est toujours pas aussi bien qu'un $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 qui vous ne pouvez pas savoir si l'exécution sera synchrone ou non. par exemple, après avoir récupéré une ressource potentiellement mise en cache : parfois, cela nécessitera un appel asynchrone à un serveur, sinon la ressource sera récupérée localement de manière synchrone.
Dans ces cas et dans tous les autres où vous avez eu une !$scope.$$phase
assurez-vous d'utiliser $scope.$evalAsync( callback )
34 votes
C'est vraiment frustrant de devoir utiliser $apply de plus en plus.
0 votes
J'obtiens également cette erreur, même si j'appelle $apply dans un callback. J'utilise une bibliothèque tierce pour accéder à des données sur leurs serveurs, je ne peux donc pas tirer parti de $http, et je ne veux pas le faire puisque je devrais réécrire leur bibliothèque pour utiliser $http.
45 votes
Utiliser
$timeout()
6 votes
Use $timeout(fn) + 1, Cela peut résoudre le problème, !$scope.$$phase n'est pas la meilleure solution.
1 votes
Enveloppez uniquement le code/appellez scope.$apply à partir de sur les délais d'attente (pas $timeout) les fonctions AJAX (pas $http) et les événements (pas
ng-*
). Si vous l'appelez à partir d'une fonction (qui est appelée via timeout/ajax/événements), assurez-vous qu'il ne s'agit pas de également en cours d'exécution sur la charge initiale.0 votes
J'obtiens cette erreur à un endroit où je n'appelle même pas $apply. J'appelle
element[0].focus();
0 votes
Essayez également d'utiliser $digest si vous le pouvez, car il ne s'exécute que dans la portée et non sur l'ensemble de l'application. Pour l'amour de la vitesse :)
0 votes
angularjs et $apply