web-dev-qa-db-fra.com

AngularJS ne rafraîchit pas ngRepeat lors de la mise à jour d'un tableau

J'ai parfois de gros problèmes pour comprendre AngularJS. J'ai donc un tableau de base dans mon contrôleur comme

$scope.items = ["a","b","c"]

Je répète dans mon modèle le tableau d'éléments ng-repeat = "élément dans les éléments". Super straightfoward jusqu'à présent. Après quelques actions UX, je souhaite transférer de nouveaux éléments dans mon tableau.

 $scope.items.Push("something");

Ainsi, 50% du temps, le nouvel élément est ajouté à la vue. Mais les 50% restants, rien ne se passe. Et c'est comme super frustrant; bc si j'emballe cela dans $ scope. $ apply (), j'ai l'erreur "$ digest déjà en cours". Envelopper cela dans $ timeout n'aide pas non plus.

Et lorsque j'inspecte la portée de mes éléments à l'aide de l'extension Chrome; Je peux voir que les nouvelles données sont là et que la valeur $ scope.items est correcte. Mais la vue ne prend tout simplement pas soin d’ajouter cela au DOM.

Merci!

31
spacenick

Vous modifiez l'étendue en dehors du cycle $digest d'angular dans 50% des cas.

S'il y a un rappel qui ne provient pas d'angularjs; (éventuellement jquery). Vous devez appeler $apply pour forcer un $digest cycle . Mais vous ne pouvez pas appeler $apply tant que vous êtes dans le cycle $digest car toutes les modifications que vous avez apportées seront déjà répercutées automatiquement.

Vous devez savoir quand le rappel n'est pas angulaire et appeler $apply seulement à ce moment-là.

Si vous ne savez pas et que vous ne pouvez pas apprendre, voici une astuce intéressante:

var applyFn = function () {
    $scope.someProp = "123";
};
if ($scope.$$phase) { // most of the time it is "$digest"
    applyFn();
} else {
    $scope.$apply(applyFn);
}
21
Umur Kontacı

Comme l'a souligné Umur Kontacı, vous apportez parfois des modifications de modèle en dehors du cycle de digestion. Cependant, au lieu de contourner ce problème et d'essayer de détecter si vous vous trouvez dans un contexte apply/digest ou non, je vous suggère de vous assurer que cela ne se produise jamais. 

La cause principale de ce problème est que votre fonction est appelée en réaction à un événement DOM. Par exemple.

jQuery('.some-element').click(function() { seeminglyProblematicCode() })

C’est là que votre $ apply () doit aller, pas dans la fonction. Sinon, votre code tout entier sera jalonné de telles distinctions tôt ou tard. En supposant qu'il y ait une portée dans le contexte de ce gestionnaire d'événements, vous pouvez écrire:

jQuery('.some-element').click(function() { 
    $scope.$apply(function() { seeminglyProblematicCode() })
})

Cependant, vous devez être conscient d'une mise en garde: lorsque vous déclenchez un événement click à partir de votre code, vous rencontrez le problème selon lequel un cycle de synthèse est déjà en cours. C'est là que vous avez besoin du timeout $. Les réponses à à cette question couvrent très bien ce problème.

4
lex82

J'avais le même problème et ma solution consistait à surveiller les contrôleurs appelés dans des directives imbriquées.

# Parent Controller
app.controller 'storeController', ($scope, products) ->

  $scope.cart = ["chicken", "pizza"]
  $scope.addToCart = (item) ->
    $scope.cart.Push item

  # from service
  products.get().then (items) ->
    $scope.products = items

# Parent Directives
app.directive 'storeContainer', ($scope, config) ->
  restrict: 'E'
  templatUrl: 'store-container.html'
  controller: 'storeController'

# Nested Directive
app.directive 'storeFront', ($scope, config) ->
  restrict: 'E'
  templatUrl: 'store-front.html'
  controller: 'storeController'

# Parent Template templates/directives/store-container.html
<div ng-repeat="item in cart">{{ item }}</div>
<strore-front></store-front>

# Nested Template templates/directives/store-front.html
<ul ng-repeat="item in products">
  <li ng-click"addToCart(item)">{{ item }}</li>
</ul>

Le bogue ici est que la directive imbriquée crée un deuxième contrôleur dans la chaîne de prototypes (une copie de storeController), auquel le modèle parent n'a pas accès. Pour résoudre, écrivez le contrôleur imbriqué comme suit:

# Nested Directive
app.directive 'storeFront', ($scope, config) ->
  restrict: 'E'
  templatUrl: 'store-front.html'

Il existe de meilleures façons de créer la chaîne d'héritage, mais cela résoudra le problème pour de nombreuses personnes apprenant AngularJS.

0
brettu