¿Cómo recorrer los elementos devueltos por una función con ng-repeat?

114

Quiero crear divs repetidamente, los elementos son objetos devueltos por una función. Sin embargo, el siguiente código informa errores: se alcanzaron 10 iteraciones de $ digest (). ¡Abortando! jsfiddle está aquí: http://jsfiddle.net/BraveOstrich/awnqm/

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

Respuestas:

195

Respuesta corta : ¿realmente necesita esa función o puede usar la propiedad? http://jsfiddle.net/awnqm/1/

Respuesta larga

Por simplicidad, describiré solo su caso: ngRepeat para una matriz de objetos. Además, omitiré algunos detalles.

AngularJS usa verificación sucia para detectar cambios. Cuando se inicia la aplicación que se ejecuta $digestpara $rootScope. $digesthará un recorrido en profundidad primero para la jerarquía del alcance . Todos los alcances tienen lista de relojes. Cada reloj tiene el último valor (inicialmente initWatchVal). Para cada alcance de todos los relojes, lo $digestejecuta, obtiene el valor actual ( watch.get(scope)) y lo compara con watch.last. Si el valor actual no es igual a watch.last(siempre para la primera comparación) se $digestestablece dirtyen true. Cuando se procesan todos los ámbitos, dirty == true $digestcomienza otro recorrido en profundidad desde $rootScope. $digesttermina cuando está sucio == falso o número de recorridos == 10. En el último caso, el error "10 $ digest () iteraciones alcanzadas". se registrará.

Ahora sobre ngRepeat. Para cada watch.getllamada, almacena objetos de la colección (valor de retorno de getEntities) con información adicional en caché ( HashQueueMappor hashKey). Para cada watch.getllamada, ngRepeatintenta obtener un objeto por su hashKeycaché. Si no existe en la memoria caché, ngRepeatlo almacena en la memoria caché, crea nuevas posibilidades, pone objeto en ella, crea elemento DOM, etc .

Ahora sobre hashKey. Generalmente hashKeyes un número único generado por nextUid(). Pero puede ser funcional . hashKeyse almacena en el objeto después de generarlo para uso futuro.

Por qué su ejemplo genera error : la función getEntities()siempre devuelve una matriz con un nuevo objeto. Este objeto no tiene hashKeyni existe en la ngRepeatcaché. Entonces ngRepeaten cada uno watch.getgenera un nuevo alcance para él con un nuevo reloj {{entity.id}}. Este primer reloj watch.gettiene watch.last == initWatchVal. Entonces watch.get() != watch.last. Entonces $digestcomienza una nueva travesía. Entonces ngRepeatcrea un nuevo alcance con un nuevo reloj. Entonces ... después de 10 recorridos obtienes un error.

¿Cómo puedes arreglarlo?

  1. No cree nuevos objetos en cada getEntities()llamada.
  2. Si necesita crear nuevos objetos, puede agregar un hashKeymétodo para ellos. Consulte este tema para ver ejemplos.

Espero que las personas que conocen los componentes internos de AngularJS me corrijan si me equivoco en algo.

Artem Andreev
fuente
4
+1 gracias por esto. Tuve el mismo problema y no pude usar una propiedad estática para ello. $$ hashKey realmente debería estar documentado en la página ngRepeat del manual IMO.
Michael Moussa
¿Alguna idea de qué cambió de 1.1.3 a 1.1.4 que afectó esto? Antes de 1.1.4 esto realmente funcionaba. No hay nada en el registro de cambios al respecto y no puedo razonar cuál es la diferencia. El comportamiento actual tiene sentido.
m59
Además, mira esto si puedes: stackoverflow.com/questions/20933261/… No estoy seguro de si mi respuesta es el camino a seguir o no ..
m59
2
Entonces, siguiendo la recomendación Do not create new objects on every getEntities() call.se puede arreglar bastante fácilmente así:<div ng-repeat="entity in entities = (entities || getEntities())">
przno
2
la solución de mi comentario anterior funciona en caso de que getEntities()siempre devuelva la misma matriz, si la matriz alguna vez cambia, no la obtendrá en elng-repeat
przno
44

Inicializar la matriz fuera de la repetición

<body ng-app>
   <div ng-init="entities = getEntities()">
       <div ng-repeat="entity in entities">
           Hello {{entity.id}}!
       </div>
   </div>
</body>
Mwayi
fuente
8
Esto no funciona si getEntities()devuelve algo diferente en el ciclo de vida del programa. Digamos, por ejemplo, que getEntities()desencadena un $http.get. Cuando el get finalmente se resuelva (realizó la llamada AJAX), entitiesya estará inicializado.
Noche al
3
De los documentos angularesThe 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
Creo que el punto principal es "inicializar la matriz fuera de la repetición" por cualquier medio ... y @Nighto buenas promesas
Mwayi
15

Esto se informó aquí y obtuvo esta respuesta:

Su captador no es idempotente y cambia el modelo (generando una nueva matriz cada vez que se llama). Esto está obligando a angular a seguir llamándolo con la esperanza de que el modelo eventualmente se estabilice, pero nunca lo hace, por lo que angular se rinde y lanza una excepción.

Los valores que devuelve el captador son iguales pero no idénticos y ese es el problema.

Puede ver que este comportamiento desaparece si mueve la matriz fuera del controlador principal:

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

porque ahora devuelve el mismo objeto cada vez. Es posible que deba rediseñar su modelo para usar una propiedad en el alcance en lugar de una función:

Lo solucionamos asignando el resultado del método del controlador a una propiedad y haciendo ng: repeat contra ella.

Dennis
fuente
Usar una propiedad puede ser la única forma si la función tiene un parámetro que cambia en cada iteración.
Stephane
7

Basado en el comentario de @przno

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

Por cierto, la segunda solución @Artem Andreev sugiere que no funciona en Angular 1.1.4 y superior, mientras que la primera no resuelve el problema. Entonces, me temo que por ahora esta es la solución menos puntiaguda sin desventajas en la funcionalidad

Agat
fuente
¿Te refieres a item.id? Si entity.id es lo que quiere decir, ¿podría explicarlo? ¡Muchas gracias!
Gerfried
Si, tienes razón. Item.ides lo que debería b. Gracias
Agat