Wie durchlaufe ich Elemente, die von einer Funktion mit ng-repeat zurückgegeben wurden?

114

Ich möchte divs wiederholt erstellen, die Elemente sind Objekte, die von einer Funktion zurückgegeben werden. Der folgende Code meldet jedoch Fehler: 10 $ Digest () - Iterationen erreicht. Abbruch! jsfiddle ist hier: http://jsfiddle.net/BraveOstrich/awnqm/

<body ng-app>
  <div ng-repeat="entity in getEntities()">
    Hello {{entity.id}}!
  </div>
</body>
Xiao Peng - ZenUML.com
quelle

Antworten:

195

Kurze Antwort : Benötigen Sie wirklich eine solche Funktion oder können Sie Eigentum verwenden? http://jsfiddle.net/awnqm/1/

Lange Antwort

Der Einfachheit halber beschreibe ich nur Ihren Fall - ngRepeat für ein Array von Objekten. Außerdem werde ich einige Details weglassen.

AngularJS verwendet Dirty Checking, um Änderungen zu erkennen. Wenn die Anwendung gestartet wird, läuft sie $digestfür $rootScope. $digestführt die Tiefenüberquerung für die Hierarchie des Bereichs durch . Alle Zielfernrohre haben eine Liste von Uhren. Jede Uhr hat (anfangs initWatchVal) den letzten Wert . Für jeden Bereich, der für alle Uhren ausgeführt $digestwird, wird der aktuelle Wert ( watch.get(scope)) abgerufen und mit verglichen watch.last. Wenn der aktuelle Wert nicht gleich ist watch.last(immer für den ersten Vergleich) $digest, wird dirtyauf gesetzt true. Wenn alle Bereiche verarbeitet werden, dirty == true $digestbeginnt eine weitere Tiefenüberquerung ab $rootScope. $digestendet, wenn Dirty == false oder Anzahl der Durchläufe == 10. Im letzteren Fall wurde der Fehler "10 $ Digest () - Iterationen erreicht" angezeigt. wird protokolliert.

Nun zu ungefähr ngRepeat. Bei jedem watch.getAufruf werden Objekte aus der Sammlung (Rückgabewert von getEntities) mit zusätzlichen Informationen im Cache ( HashQueueMapvon hashKey) gespeichert . Bei jedem watch.getAufruf wird ngRepeatversucht, das Objekt anhand seines hashKeyCaches abzurufen. Wenn es nicht im Cache vorhanden ist , ngRepeatspeichert sie im Cache, schafft neue Möglichkeiten, Objekt stellt auf der es schafft DOM - Element, etc .

Nun zu ungefähr hashKey. Normalerweise hashKeywird eine eindeutige Nummer von generiert nextUid(). Aber es kann Funktion sein . hashKeywird nach dem Generieren im Objekt für die zukünftige Verwendung gespeichert.

Warum Ihr Beispiel einen Fehler generiert : Die Funktion gibt getEntities()immer ein Array mit einem neuen Objekt zurück. Dieses Objekt hat hashKeyund existiert nicht im ngRepeatCache. So erzeugt ngRepeatjeder watch.getneue Spielraum dafür mit neuer Uhr für {{entity.id}}. Diese Uhr auf den ersten watch.gethat watch.last == initWatchVal. Also watch.get() != watch.last. So $digestbeginnt neue Traverse. So ngRepeatentsteht mit neuer Uhr ein neuer Spielraum. Also ... nach 10 Durchläufen erhalten Sie einen Fehler.

Wie Sie es beheben können

  1. Erstellen Sie nicht bei jedem getEntities()Aufruf neue Objekte .
  2. Wenn Sie neue Objekte erstellen müssen, können Sie eine hashKeyMethode für diese hinzufügen . Beispiele finden Sie in diesem Thema .

Hoffe, dass Leute, die AngularJS-Interna kennen, mich korrigieren, wenn ich in etwas falsch liege.

Artem Andreev
quelle
4
+1 danke dafür. Ich hatte das gleiche Problem und konnte keine statische Eigenschaft dafür verwenden. $$ hashKey sollte wirklich auf der ngRepeat-Seite des Handbuchs IMO dokumentiert sein.
Michael Moussa
Irgendeine Idee, was sich von 1.1.3 auf 1.1.4 geändert hat, was dies beeinflusst hat? Vor 1.1.4 hat dies tatsächlich funktioniert. Es gibt nichts im Changelog und ich kann nicht herausfinden, was der Unterschied ist. Das aktuelle Verhalten macht Sinn.
m59
Überprüfen Sie dies auch, wenn Sie könnten: stackoverflow.com/questions/20933261/… Ich bin nicht sicher, ob meine Antwort der
richtige
2
So kann die Empfehlung dazu Do not create new objects on every getEntities() call.ziemlich einfach wie <div ng-repeat="entity in entities = (entities || getEntities())">
folgt
2
Die Lösung aus meinem vorherigen Kommentar funktioniert für den Fall, dass getEntities()immer das gleiche Array zurückgegeben wird. Wenn sich das Array jemals ändert, erhalten Sie es nicht imng-repeat
przno
44

Initialisieren Sie das Array außerhalb der Wiederholung

<body ng-app>
   <div ng-init="entities = getEntities()">
       <div ng-repeat="entity in entities">
           Hello {{entity.id}}!
       </div>
   </div>
</body>
Mwayi
quelle
8
Dies funktioniert nicht, wenn getEntities()im Lebenszyklus des Programms etwas anderes zurückgegeben wird. Sagen wir zum Beispiel, dass getEntities()ein $http.get. Wenn die endgültige Auflösung (Sie haben den AJAX-Aufruf durchgeführt) entitiesbereits initialisiert ist.
Nighto
3
Aus den eckigen DokumentenThe only appropriate use of ngInit is for aliasing special properties of ngRepeat. Besides this case, you should use controllers rather than ngInit to initialize values on a scope.
Blowsie
Ich denke, der Hauptpunkt ist, "das Array außerhalb der Wiederholung zu initialisieren" mit welchen Mitteln auch immer ... und @Nighto guten Aufruf zu Versprechungen
Mwayi
15

Dies wurde hier gemeldet und erhielt diese Antwort:

Ihr Getter ist nicht idempotent und ändert das Modell (indem Sie bei jedem Aufruf ein neues Array generieren). Dies zwingt Angular dazu, es weiter zu nennen, in der Hoffnung, dass sich das Modell irgendwann stabilisiert, aber es gibt niemals so auf, dass Angular aufgibt und eine Ausnahme auslöst.

Die Werte, die der Getter zurückgibt, sind gleich, aber nicht identisch, und das ist das Problem.

Sie können sehen, dass dieses Verhalten verschwindet, wenn Sie das Array außerhalb des Hauptcontrollers verschieben:

var array = [{id:'angularjs'}];
function Main($scope) {
    $scope.getEntities = function(){return array;};
};

denn jetzt gibt es jedes Mal das gleiche Objekt zurück. Möglicherweise müssen Sie Ihr Modell neu erstellen, um eine Eigenschaft für den Bereich anstelle einer Funktion zu verwenden:

Wir haben es umgangen, indem wir das Ergebnis der Controller-Methode einer Eigenschaft zugewiesen und ng: repeat dagegen ausgeführt haben.

Dennis
quelle
Die Verwendung einer Eigenschaft kann der einzige Weg sein, wenn sich bei der Funktion bei jeder Iteration ein Parameter ändert.
Stephane
7

Basierend auf @przno Kommentar

<body ng-app>
  <div ng-repeat="item in t = angular.equals(t, getEntities()) ? t : getEntities()">
    Hello {{item.id}}!
  </div>
</body>

Übrigens: Die zweite Lösung @Artem Andreev schlägt vor, dass Angular 1.1.4 und höher nicht funktioniert, während die erste das Problem nicht löst. Ich fürchte, dies ist die weniger stachelige Lösung ohne Nachteile in der Funktionalität

Agat
quelle
Meinst du item.id? Wenn entity.id das ist, was Sie meinen, können Sie dies bitte erklären? Danke vielmals!
Gerfried
Ja du hast Recht. Item.idist was es sollte b. Vielen Dank
Agat