Come scorrere gli elementi restituiti da una funzione con ng-repeat?


114

Voglio creare div ripetutamente, gli elementi sono oggetti restituiti da una funzione. Tuttavia, il codice seguente riporta errori: 10 $ digest () iterazioni raggiunte. Interruzione! jsfiddle è qui: http://jsfiddle.net/BraveOstrich/awnqm/

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

Risposte:


195

Risposta breve : hai davvero bisogno di tale funzione o puoi usare la proprietà? http://jsfiddle.net/awnqm/1/

Risposta lunga

Per semplicità descriverò solo il tuo caso - ngRepeat per array di oggetti. Inoltre, ometterò alcuni dettagli.

AngularJS utilizza il controllo sporco per rilevare le modifiche. Quando l'applicazione viene avviata, viene eseguita $digestper $rootScope. $digesteseguirà il primo attraversamento in profondità per la gerarchia dell'ambito . Tutti gli ambiti hanno un elenco di orologi. Ogni orologio ha l'ultimo valore (inizialmente initWatchVal). Per ogni ambito di tutti gli orologi lo $digestesegue, ottiene il valore corrente ( watch.get(scope)) e lo confronta con watch.last. Se il valore corrente non è uguale a watch.last(sempre per il primo confronto) viene $digestimpostato dirtysu true. Quando tutti gli ambiti vengono elaborati, se dirty == true $digestavvia un altro attraversamento in profondità da $rootScope.$digesttermina quando sporco == false o numero di attraversamenti == 10. In quest'ultimo caso, l'errore "10 $ digest () iterazioni raggiunte". sarà registrato.

Ora circa ngRepeat. Per ogni watch.getchiamata memorizza gli oggetti dalla raccolta (restituendo il valore di getEntities) con informazioni aggiuntive nella cache ( HashQueueMapby hashKey). Per ogni watch.getchiamata ngRepeatcerca di ottenere l'oggetto dalla sua hashKeycache. Se non esiste nella cache, lo ngRepeatmemorizza nella cache, crea un nuovo ambito, inserisce l'oggetto su di esso, crea l'elemento DOM, ecc .

Ora circa hashKey. Di solito hashKeyè un numero univoco generato da nextUid(). Ma può essere una funzione . hashKeyviene memorizzato nell'oggetto dopo la generazione per un utilizzo futuro.

Perché il tuo esempio genera errore : la funzione getEntities()restituisce sempre un array con un nuovo oggetto. Questo oggetto non ha hashKeye non esiste nella ngRepeatcache. Quindi ngRepeatognuno watch.getgenera nuove possibilità per esso con un nuovo orologio per {{entity.id}}. Questo orologio per primo watch.getha watch.last == initWatchVal. Quindi watch.get() != watch.last. $digestInizia così una nuova traversata. Quindi ngRepeatcrea un nuovo ambito con il nuovo orologio. Quindi ... dopo 10 traversate ottieni un errore.

Come puoi risolverlo

  1. Non creare nuovi oggetti a ogni getEntities()chiamata.
  2. Se hai bisogno di creare nuovi oggetti puoi aggiungere un hashKeymetodo per loro. Vedere questo argomento per esempi.

Spero che le persone che conoscono gli interni di AngularJS mi correggano se sbaglio in qualcosa.


4
+1 grazie per questo. Ho avuto lo stesso problema e non ho potuto utilizzare una proprietà statica per questo. $$ hashKey dovrebbe davvero essere documentato nella pagina ngRepeat del manuale IMO.
Michael Moussa

Qualche idea su cosa sia cambiato da 1.1.3 a 1.1.4 che abbia influito su questo? Prima della 1.1.4 funzionava effettivamente. Non c'è niente nel log delle modifiche e non riesco a capire quale sia la differenza. Il comportamento attuale ha senso.
m59

Inoltre, dai un'occhiata se puoi: stackoverflow.com/questions/20933261/… Non sono sicuro che la mia risposta sia la strada da percorrere o meno ..
m59

2
Quindi, seguendo la raccomandazione Do not create new objects on every getEntities() call., può essere risolto abbastanza facilmente in questo modo:<div ng-repeat="entity in entities = (entities || getEntities())">
przno

2
la soluzione del mio commento precedente funziona nel caso in cui getEntities()restituisca sempre lo stesso array, se l'array cambia mai, non lo otterrai nelng-repeat
przno

44

Inizializza l'array al di fuori della ripetizione

<body ng-app>
   <div ng-init="entities = getEntities()">
       <div ng-repeat="entity in entities">
           Hello {{entity.id}}!
       </div>
   </div>
</body>

8
Questo non funziona se getEntities()restituisce qualcosa di diverso nel ciclo di vita del programma. Supponiamo, ad esempio, che getEntities()attivi un file $http.get. Quando il get finalmente risolto (hai effettuato la chiamata AJAX), entitiessarà già inizializzato.
Quasi il

3
Dai documenti angolariThe 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

Penso che il punto principale sia "inizializzare l'array al di fuori della ripetizione" con qualsiasi mezzo ... e @Nighto good call on promises
Mwayi

15

Questo è stato segnalato qui e ha ottenuto questa risposta:

Il tuo getter non è idempotente e cambia il modello (generando un nuovo array ogni volta che viene chiamato). Questo sta costringendo Angular a continuare a chiamarlo nella speranza che il modello alla fine si stabilizzi, ma non lo fa mai, quindi Angular si arrende e genera un'eccezione.

I valori restituiti dal getter sono uguali ma non identici e questo è il problema.

È possibile vedere questo comportamento scomparire se si sposta l'array fuori dal controller principale:

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

perché ora restituisce ogni volta lo stesso oggetto. Potrebbe essere necessario riprogettare il modello per utilizzare una proprietà sull'ambito invece di una funzione:

Abbiamo aggirato il problema assegnando il risultato del metodo del controller a una proprietà e facendo ng: repeat contro di essa.


L'utilizzo di una proprietà può essere l'unico modo se la funzione ha un parametro che cambia ad ogni iterazione.
Stephane

7

Basato sul commento di @przno

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

La seconda soluzione BTW @Artem Andreev suggerisce che non funziona in Angular 1.1.4 e versioni successive, mentre la prima non risolve il problema. Quindi, temo che per ora questa sia la soluzione meno spinosa senza svantaggi in termini di funzionalità


Intendi item.id? Se entity.id è ciò che intendi, potresti spiegarlo per favore? Grazie molto!
Gerfried

Si hai ragione. Item.idè quello che dovrebbe b. Grazie
Agat
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.