web-dev-qa-db-fra.com

Pourquoi utilise-t-on si (! $ Scope. $$ phase) $ scope. $ Apply () un anti-pattern?

Parfois, j'ai besoin d'utiliser $scope.$apply dans mon code et génère parfois une erreur "digérer déjà en cours". J'ai donc commencé à trouver un moyen de contourner ce problème et à trouver la question suivante: AngularJS: Empêcher l'erreur $ digest déjà en cours lors de l'appel de $ scope. $ Apply () . Cependant, dans les commentaires (et sur le angular), vous pouvez lire:

Ne le faites pas si (! $ Scope. $$ phase) $ scope. $ Apply (), cela signifie que votre $ scope. $ Apply () n'est pas assez élevé dans la pile d'appels.

Alors maintenant, j'ai deux questions:

  1. Pourquoi est-ce exactement un anti-modèle?
  2. Comment puis-je utiliser en toute sécurité $ scope. $ Apply?

Une autre "solution" pour éviter l'erreur "digérer déjà en cours" semble utiliser $ timeout:

$timeout(function() {
  //...
});

Est-ce la voie à suivre? Est-ce plus sûr? Voici donc la vraie question: comment puis-je entièrement éliminer la possibilité d’une erreur "digérer déjà en cours"?

PS: Je n’utilise que $ scope. $ S’applique aux callbacks non angularjs qui ne sont pas synchrones. (autant que je sache, il s'agit de situations dans lesquelles vous devez utiliser $ scope. $ s'applique si vous souhaitez que vos modifications soient appliquées)

91
Dominik Goltermann

Après avoir creusé un peu plus, j'ai pu résoudre la question de savoir s'il était toujours prudent d'utiliser $scope.$apply. La reponse courte est oui.

Longue réponse:

En raison de la manière dont votre navigateur exécute Javascript, il n'est pas possible que deux appels digest soient en collision par hasard .

Le code JavaScript que nous écrivons ne fonctionne pas tous en une fois, mais s’exécute à tour de rôle. Chacun de ces tours tourne sans interruption du début à la fin, et lorsqu'un tour est lancé, rien d'autre ne se passe dans notre navigateur. (de http://jimhoskins.com/2012/12/17/angularjs-and-apply.html )

Par conséquent, l'erreur "digérer déjà en cours" ne peut se produire que dans un cas: lorsqu'un $ apply est émis dans un autre $ apply, par exemple:

$scope.apply(function() {
  // some code...
  $scope.apply(function() { ... });
});

Cette situation peut ne pas se produire si on utilise $ scope.apply dans un Rappel pur non angularjs, comme par exemple le rappel de setTimeout. Donc, le code suivant est 100% à l'épreuve des balles et il n'y a aucun besoin de faire un if (!$scope.$$phase) $scope.$apply()

setTimeout(function () {
    $scope.$apply(function () {
        $scope.message = "Timeout called!";
    });
}, 2000);

même celui-ci est sûr:

$scope.$apply(function () {
    setTimeout(function () {
        $scope.$apply(function () {
            $scope.message = "Timeout called!";
        });
    }, 2000);
});

Qu'est-ce qui est [~ # ~] pas [~ # ~] sûr (parce que $ timeout - comme tous les helpers angularjs - appelle déjà $scope.$apply pour vous):

$timeout(function () {
    $scope.$apply(function () {
        $scope.message = "Timeout called!";
    });
}, 2000);

Cela explique également pourquoi l'utilisation de if (!$scope.$$phase) $scope.$apply() est un anti-motif. Vous n'en avez simplement pas besoin si vous utilisez $scope.$apply Correctement: Dans un callback pur en js comme setTimeout par exemple.

Lisez http://jimhoskins.com/2012/12/17/angularjs-and-apply.html pour une explication plus détaillée.

109
Dominik Goltermann

C'est très certainement un anti-modèle maintenant. J'ai vu un résumé exploser même si vous vérifiez la phase $$. Vous n'êtes simplement pas censé accéder à l'API interne dénotée par $$ préfixes.

Tu devrais utiliser

 $scope.$evalAsync();

car c'est la méthode préférée dans Angular ^ 1.4 et est spécifiquement exposé en tant qu'API pour la couche d'application.

16
FlavorScape

Dans tous les cas, lorsque votre résumé est en cours et que vous poussez un autre service à digérer, cela donne simplement une erreur, c’est-à-dire que le résumé est déjà en cours. donc pour remédier à cela, vous avez deux options. vous pouvez vérifier si un autre résumé est en cours, comme une interrogation.

le premier

if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') {
    $scope.$apply();
}

si la condition ci-dessus est vraie, vous pouvez appliquer votre $ scope. $ ne s'applique pas aux autres méthodes et

deuxième solution utilise $ timeout

$timeout(function() {
  //...
})

il ne laissera pas l’autre digérer commencer jusqu’à ce que $ timeout ait terminé son exécution.

9
Lalit Sachdeva

scope.$apply déclenche un $digest cycle qui est fondamental pour la liaison de données bidirectionnelle

UNE $digest le cycle vérifie les objets, c’est-à-dire les modèles (pour être précis, $watch) attaché à $scope pour évaluer si leurs valeurs ont changé et si une détection est détectée, les étapes nécessaires à la mise à jour de la vue sont alors nécessaires.

Maintenant, quand vous utilisez $scope.$apply vous faites face à une erreur "Déjà en cours" il est donc évident qu'un $ digest est en cours d'exécution, mais qu'est-ce qui l'a déclenché?

ans -> chaque $http appels, tout ng-clic, répéter, montrer, masquer etc. déclenchent un $digest _ cycle ET LA PIRE PIÈCE CELLE DE CHAQUE $ ÉTENDUE.

c'est-à-dire que votre page a 4 contrôleurs ou directives A, B, C, D

Si vous avez 4 $scope propriétés dans chacune d’elles alors vous avez un total de 16 propriétés $ scope sur votre page.

Si vous déclenchez $scope.$apply dans le contrôleur D puis un $digest le cycle vérifiera les 16 valeurs !!! plus toutes les propriétés $ rootScope.

Réponse ->mais $scope.$digest déclenche un $digest sur enfant et même portée, il ne vérifie que 4 propriétés. Donc, si vous êtes sûr que les changements dans D n’affecteront pas A, B, C, utilisez $scope.$digest pas $scope.$apply.

Donc, un simple clic-ng ou ng-show/hide pourrait déclencher un $digest cycle sur plus de 100 propriétés même lorsque l'utilisateur a aucun événement déclenché!

9
Rishul Matta

Utilisation $timeout, c'est la voie recommandée.

Mon scénario est que je dois modifier les éléments de la page en fonction des données que j'ai reçues d'un WebSocket. Et comme il est en dehors de Angular, sans le timeout $, le seul modèle sera modifié, mais pas la vue. Parce que Angular ne sait pas que cette donnée a été modifiée. $timeout dit fondamentalement Angular) pour effectuer le changement lors du prochain cycle de $ digest.

J'ai aussi essayé la suite et ça marche. La différence pour moi est que $ timeout est plus clair.

setTimeout(function(){
    $scope.$apply(function(){
        // changes
    });
},0)
0
James J. Ye