Además de las respuestas aceptadas anteriores, creé un filtro genérico 'groupBy' usando la biblioteca underscore.js.
JSFiddle (actualizado):
http://jsfiddle.net/TD7t3/
El filtro
app.filter('groupBy', function() {
return _.memoize(function(items, field) {
return _.groupBy(items, field);
}
);
});
Tenga en cuenta la llamada 'memorizar'. Este método de subrayado almacena en caché el resultado de la función y evita que angular evalúe la expresión del filtro cada vez, evitando así que angular alcance el límite de iteraciones de resumen.
El html
<ul>
<li ng-repeat="(team, players) in teamPlayers | groupBy:'team'">
{{team}}
<ul>
<li ng-repeat="player in players">
{{player.name}}
</li>
</ul>
</li>
</ul>
Aplicamos nuestro filtro 'groupBy' en la variable de alcance teamPlayers, en la propiedad 'team'. Nuestro ng-repeat recibe una combinación de (clave, valores []) que podemos usar en nuestras siguientes iteraciones.
Actualización 11 de junio de 2014
Expandí el grupo por filtro para tener en cuenta el uso de expresiones como clave (por ejemplo, variables anidadas). El servicio de análisis angular es bastante útil para esto:
El filtro (con soporte de expresión)
app.filter('groupBy', function($parse) {
return _.memoize(function(items, field) {
var getter = $parse(field);
return _.groupBy(items, function(item) {
return getter(item);
});
});
});
El controlador (con objetos anidados)
app.controller('homeCtrl', function($scope) {
var teamAlpha = {name: 'team alpha'};
var teamBeta = {name: 'team beta'};
var teamGamma = {name: 'team gamma'};
$scope.teamPlayers = [{name: 'Gene', team: teamAlpha},
{name: 'George', team: teamBeta},
{name: 'Steve', team: teamGamma},
{name: 'Paula', team: teamBeta},
{name: 'Scruath of the 5th sector', team: teamGamma}];
});
El html (con expresión sortBy)
<li ng-repeat="(team, players) in teamPlayers | groupBy:'team.name'">
{{team}}
<ul>
<li ng-repeat="player in players">
{{player.name}}
</li>
</ul>
</li>
JSFiddle:
http://jsfiddle.net/k7fgB/2/