web-dev-qa-db-fra.com

Quand est-il prudent d'utiliser $ scope. $ Apply ()?

Je suppose que le titre est à peu près clair ce que je demande. J'ai créé ce violon: http://jsfiddle.net/Sourabh_/HB7LU/13142/

Dans le violon, j'ai essayé de reproduire un scénario async. Ceci est juste un exemple, mais dans un appel AJAX si je n'utilise pas $scope.$apply(), la liste n'est pas mise à jour. Je souhaite savoir s'il est prudent d'utiliser $scope.$apply() chaque fois que je passe un appel AJAX pour mettre à jour une liste ou existe-t-il un autre mécanisme que je peux utiliser?

Code que j'ai écrit pour reproduire le scénario (comme en violon):

HTML

<div ng-controller="MyCtrl">
  <li ng-repeat="item in items">
    {{item.name}}
  </li>
  <button ng-click="change()">Change</button>
</div>

JS

var myApp = angular.module('myApp',[]);

function MyCtrl($scope) {
  $scope.items = [{name : "abc"},{name : "xyz"},{name : "cde"}];

  $scope.change = function(){
    test(function(testItem){
      $scope.items = testItem;
      //$scope.$apply();
    })
  }
  function test(callback){
    var testItem = [
                    {name : "mno"},
                    {name : "pqr"},
                    {name :   "ste"}
                   ];
    setTimeout(function(){callback(testItem)},2000);
  }
}
12
Zee

Si vous souhaitez immidifier un API-Rest-Call, utilisez la promise retournée dans votre Controller au lieu de définir la portée de l'appel-rest.

$http.get('uri')
  .success(function(data) {
    $scope.items = data
});

Évitez d’utiliser $apply(). Depuis le Angular GitHub Repo

$scope.$apply() doit se produire aussi près que possible de la liaison d'événement asynchrone.

NE PAS saupoudrer au hasard dans votre code. Si vous faites si (!$scope.$$phase) $scope.$apply() c'est parce que vous n'êtes pas assez haut Dans la pile d'appels.

A votre question:

  • Si vous vous trouvez dans une situation où vous avez besoin de $ apply (), repensez votre structure.
  • Juste pour des raisons de sécurité: n'utilisez jamais $apply()
5
Bastian Gruber

Edit Il n'était pas clair que l'OP essayait de se moquer d'un appel backend. Même dans ce cas, utiliser le service $timeout est un excellent moyen d’éviter d’appeler $scope.$apply manuellement et constitue une solution plus générale que l’utilisation d’une promesse (dans les cas où vous n'appelez pas $http, il n’est pas toujours judicieux de forcer. vos changements dans le prochain cycle en les enveloppant d’une promesse).


Mettez à jour votre code pour utiliser le service $ timeout et il devrait fonctionner sans avoir à appeler $apply.

$timeout est un wrapper autour de la setTimeout native avec une différence importante: $timeout retardera l'exécution au moins jusqu'à l'exécution du prochain cycle $digest.

Donc, passer sans délai retardera toujours l'exécution jusqu'au prochain cycle. Passer en 2000 retardera l'exécution jusqu'au cycle suivant après 2000 ms.

Par conséquent, il s'agit d'un truc simple pour vous assurer que vos modifications sont bien enregistrées par Angular sans avoir à appeler $apply manuellement (ce qui est considéré comme dangereux dans tous les cas).

function MyCtrl($scope, $timeout) {
  $scope.items = [{name : "abc"},{name : "xyz"},{name : "cde"}];

  $scope.change = function(){
    test(function(testItem){
      $scope.items = testItem;
      //$scope.$apply();
    })
  }
  function test(callback){
    var testItem = [
                    {name : "mno"},
                    {name : "pqr"},
                    {name :   "ste"}
                   ];
    $timeout(function(){callback(testItem)},2000);
  }
}
6
thomaux

Le $ apply , doit être utilisé lorsque le code n'est pas exécuté dans une boucle d'empreinte angulaire. Dans des circonstances normales, nous n’aurons pas besoin de l’utiliser, mais nous pourrions en avoir besoin si nous avons un code appelé à partir d’un gestionnaire d’événements jQuery ou de méthodes telles que setTimeout(). Même si vous avez une fonction appelée à partir d'une autre fonction angulaire telle qu'un gestionnaire de variable watch ou angulaire, vous n'avez pas besoin d'utiliser $ apply () car ces scripts sont exécutés dans le cycle de résumé.

Un moyen sûr est de vérifier le $scope.$$phase param avant d'appeler $ scope. $ Apply () like

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

Dans votre cas, mais vous pouvez utiliser $ timeout comme suggéré dans la réponse

3
Arun P Johny

Vous devez utiliser $ apply à chaque fois que vous utilisez quelque chose qui n'est pas "angulaire", comme Anzeo a parlé de $ timeout.
Par exemple, si vous utilisez http de jQuery au lieu de $ http de angular, vous devrez ajouter $ scope.

2
Ivan Toncev

Toutes les réponses ci-dessus donnent des informations mais elles ne répondent pas aux quelques doutes que j'avais ou du moins je ne les comprenais pas. Alors je donne le mien. 

Il est très clairement indiqué dans les documents angulaires quand utiliser $ apply

les rappels de $ http ou $ timeout ou ng-click, ng -..... sont encapsulés par $ apply (). Donc, quand les gens disent que vous n’êtes pas obligé d’utiliser $ apply () quand vous faites les choses de façon angulaire, c’est ça. Cependant, l'une des réponses mentionne que les gestionnaires d'événements angulaires sont également entourés de $ apply (). Ce n'est pas vrai ou par cela, l'utilisateur entend simplement un type d'événements ng-click (encore une fois ng -....). Si l'événement est diffusé sur rootScope (ou sur une étendue quelconque) en dehors de $ http, $ timeout ou ng-click, par exemple à partir d'un service personnalisé, vous devez utiliser $ apply () sur votre portée bien que $ rootScope . $ broadcast est aussi une façon angulaire de faire les choses. dans la plupart des scénarios, nous n'en aurons pas besoin, car l'état de l'application change lorsque quelque chose se produit. c'est-à-dire que les clics, le changement de sélection, etc ... et qu'ils sont en termes angulaires utilisent déjà $ apply () lorsque nous utilisons respectivement ng-click ng-change. La gestion des événements côté serveur à l'aide de signalr ou socket.io et lors de l'écriture de directives personnalisées nécessitant de modifier uniquement le champ d'application de la directive sont quelques exemples où l'utilisation de $ apply () est essentielle.

Comme @gruberb l'a souligné dans les commentaires, si vous tentiez de vous moquer d'un appel REST, vous feriez mieux d'utiliser une promesse que $apply.

Pour cela, vous devez utiliser $ q service pour créer et retourner une promesse. Ensuite, appelez-le simplement et travaillez avec votre résultat en appelant la méthode then() sur la promesse retournée.

function MyCtrl($scope, $q) {
    $scope.items = [{name : "abc"},{name : "xyz"},{name : "cde"}];

    $scope.change = function(){

        test().then(function (items) {
            $scope.items = items;
            $scope.$apply();
        });
    };

    function test() {
        var defered = $q.defer();

        var testItem = [
            {name : "mno"},
            {name : "pqr"},
            {name :   "ste"}
        ];

        setTimeout(function() {
            defered.resolve(testItem);
        },2000);

        return defered.promise;
    }
}
0
Blackus