Jak przechodzić przez elementy zwrócone przez funkcję z ng-repeat?

114

Chcę wielokrotnie tworzyć elementy div, elementy są obiektami zwracanymi przez funkcję. Jednak następujący kod raportuje błędy: Osiągnięto 10 $ straw () iteracji. Przerwanie! jsfiddle jest tutaj: http://jsfiddle.net/BraveOstrich/awnqm/

<body ng-app>
  <div ng-repeat="entity in getEntities()">
    Hello {{entity.id}}!
  </div>
</body>
Xiao Peng - ZenUML.com
źródło

Odpowiedzi:

195

Krótka odpowiedź : czy naprawdę potrzebujesz takiej funkcji, czy możesz skorzystać z właściwości? http://jsfiddle.net/awnqm/1/

Długa odpowiedź

Dla uproszczenia opiszę tylko Twój przypadek - ngRepeat dla tablicy obiektów. Pominę też kilka szczegółów.

AngularJS używa brudnego sprawdzania do wykrywania zmian. Po uruchomieniu aplikacja działa $digestdla $rootScope. $digestwykona przemierzanie najpierw w głąb hierarchii zakresu . Wszystkie lunety mają listę zegarków. Każdy zegarek ma ostatnią wartość (początkowo initWatchVal). Dla każdego zakresu dla wszystkich zegarków $digesturuchamia go, pobiera bieżącą wartość ( watch.get(scope)) i porównuje ją z watch.last. Jeśli bieżąca wartość nie jest równa watch.last(zawsze przy pierwszym porównaniu) $digestustawia się dirtyna true. Gdy wszystkie zakresy są przetwarzane, jeśli dirty == true $digestrozpoczyna się kolejne przejście w głąb $rootScope. $digestkończy się, gdy dirty == false lub liczba przejść == 10. W tym drugim przypadku błąd „Osiągnięto 10 $ Digest () iteracji”. będą rejestrowane.

Teraz o ngRepeat. Dla każdego watch.getwywołania przechowuje obiekty z kolekcji (zwracając wartość getEntities) z dodatkowymi informacjami w pamięci podręcznej ( HashQueueMapwg hashKey). Dla każdego watch.getwywołania ngRepeatpróbuje pobrać obiekt hashKeyz pamięci podręcznej. Jeśli nie istnieje w pamięci podręcznej, ngRepeatprzechowuje ją w pamięci podręcznej, tworzy nowy zakres, umieszcza na nim obiekt, tworzy element DOM itp .

Teraz o hashKey. Zwykle hashKeyjest to unikalny numer generowany przez nextUid(). Ale może to być funkcja . hashKeyjest przechowywany w obiekcie po wygenerowaniu do wykorzystania w przyszłości.

Dlaczego twój przykład generuje błąd : funkcja getEntities()zawsze zwraca tablicę z nowym obiektem. Ten obiekt nie ma hashKeyi nie istnieje w ngRepeatpamięci podręcznej. Tak więc ngRepeatna każdym watch.getgeneruje nowy zakres z nowym zegarkiem dla {{entity.id}}. Ten zegarek na początku watch.getma watch.last == initWatchVal. A więc watch.get() != watch.last. Tak $digestzaczyna się nowy trawers. ngRepeatTworzy więc nowy zakres z nowym zegarkiem. Więc ... po 10 trawersach pojawia się błąd.

Jak możesz to naprawić

  1. Nie twórz nowych obiektów przy każdym getEntities()połączeniu.
  2. Jeśli potrzebujesz stworzyć nowe obiekty, możesz dodać hashKeydo nich metodę. Przykłady można znaleźć w tym temacie .

Mam nadzieję, że ludzie, którzy znają wnętrze AngularJS, poprawią mnie, jeśli się w czymś pomylę.

Artem Andreev
źródło
4
+1 dziękuję za to. Miałem ten sam problem i nie mogłem użyć do tego właściwości statycznej. $$ hashKey naprawdę powinno być udokumentowane na stronie ngRepeat instrukcji IMO.
Michael Moussa
Masz jakiś pomysł, co zmieniło się z wersji 1.1.3 na 1.1.4, co miało na to wpływ? Przed 1.1.4 to faktycznie działało. W dzienniku zmian nie ma nic na ten temat i nie mogę zrozumieć, jaka jest różnica. Obecne zachowanie ma sens.
m59
Sprawdź też, czy możesz: stackoverflow.com/questions/20933261/… Nie jestem pewien, czy moja odpowiedź jest dobra, czy nie ..
m59
2
Czyli zgodnie z zaleceniem Do not create new objects on every getEntities() call.można to naprawić dość łatwo w ten sposób:<div ng-repeat="entity in entities = (entities || getEntities())">
przno
2
rozwiązanie z mojego poprzedniego komentarza działa w przypadku, gdy getEntities()zawsze zwraca tę samą tablicę, jeśli tablica kiedykolwiek się zmieni, nie dostaniesz jej wng-repeat
przno
44

Zainicjuj tablicę poza powtórzeniem

<body ng-app>
   <div ng-init="entities = getEntities()">
       <div ng-repeat="entity in entities">
           Hello {{entity.id}}!
       </div>
   </div>
</body>
Mwayi
źródło
8
To nie działa, jeśli getEntities()zwraca coś innego w cyklu życia programu. Załóżmy na przykład, że getEntities()wyzwala to plik $http.get. Kiedy get w końcu się rozwiąże (wykonałeś połączenie AJAX), entitieszostanie już zainicjowany.
Nighto
3
From the The 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.
angular Docs
Myślę, że głównym celem jest „zainicjowanie tablicy poza powtórzeniem” w jakikolwiek sposób ... i @Nighto good call on obietnice
Mwayi
15

Zostało to zgłoszone tutaj i otrzymało tę odpowiedź:

Twój getter nie jest idempotentny i zmienia model (generując nową tablicę za każdym razem, gdy jest wywoływana). To zmusza Angulara do ciągłego nazywania go w nadziei, że model w końcu się ustabilizuje, ale nigdy tak nie robi. Angular poddaje się i rzuca wyjątek.

Wartości zwracane przez funkcję pobierającą są równe, ale nie identyczne i na tym polega problem.

Możesz zobaczyć, że to zachowanie zniknie, jeśli przeniesiesz tablicę poza kontroler główny:

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

ponieważ teraz zwraca za każdym razem ten sam obiekt. Może być konieczne ponowne zaprojektowanie modelu, aby użyć właściwości w zakresie zamiast funkcji:

Obejrzeliśmy to, przypisując wynik metody kontrolera do właściwości i wykonując ng: powtórz przeciwko niemu.

Dennis
źródło
Użycie właściwości może być jedynym sposobem, jeśli funkcja ma parametr zmieniający się przy każdej iteracji.
Stephane
7

Na podstawie komentarza @przno

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

Przy okazji, drugie rozwiązanie @Artem Andreev sugeruje, że nie działa w Angular 1.1.4 i nowszych, natomiast pierwsze nie rozwiązuje problemu. Na razie obawiam się, że jest to mniej ostre rozwiązanie bez wad funkcjonalności

Agat
źródło
Czy masz na myśli item.id? Jeśli chodzi o entity.id, czy możesz wyjaśnić? Wielkie dzięki!
Gerfried
Tak masz rację. Item.idjest tym, co powinno b. Dzięki
Agat