AngularJS ordenando por propiedad

93

Lo que estoy tratando de hacer es ordenar algunos datos por propiedad. Aquí hay un ejemplo que debería funcionar, pero no funciona.

Parte HTML:

<div ng-app='myApp'>
    <div ng-controller="controller">
    <ul>
        <li ng-repeat="(key, value) in testData | orderBy:'value.order'">
            {{value.order}}. {{key}} -> {{value.name}}
        </li>
    </ul>
    </div>
</div>

Parte JS:

var myApp = angular.module('myApp', []);

myApp.controller('controller', ['$scope', function ($scope) {

    $scope.testData = {
        C: {name:"CData", order: 1},
        B: {name:"BData", order: 2},
        A: {name:"AData", order: 3},
    }

}]);

Y el resultado:

  1. A -> AData
  2. B -> BData
  3. C -> CData

... que en mi humilde opinión debería verse así:

  1. C -> CData
  2. B -> BData
  3. A -> AData

¿Me perdí algo (aquí está JSFiddle listo para experimentar)?

PrimosK
fuente

Respuestas:

148

El filtro orderBy de AngularJS solo admite matrices, sin objetos. Por lo tanto, debe escribir un filtro pequeño propio, que lo clasifica por usted.

O cambie el formato de los datos que maneja (si tiene influencia en eso). Una matriz que contiene objetos se puede ordenar por filtro nativo orderBy.

Aquí está mi filtro orderObjectBy para AngularJS:

app.filter('orderObjectBy', function(){
 return function(input, attribute) {
    if (!angular.isObject(input)) return input;

    var array = [];
    for(var objectKey in input) {
        array.push(input[objectKey]);
    }

    array.sort(function(a, b){
        a = parseInt(a[attribute]);
        b = parseInt(b[attribute]);
        return a - b;
    });
    return array;
 }
});

Uso en su opinión:

<div class="item" ng-repeat="item in items | orderObjectBy:'position'">
    //...
</div>

El objeto necesita en este ejemplo un atributo de posición, pero tiene la flexibilidad de usar cualquier atributo en los objetos (que contengan un número entero), solo por definición a la vista.

Ejemplo JSON:

{
    "123": {"name": "Test B", "position": "2"},
    "456": {"name": "Test A", "position": "1"}
}

Aquí hay un violín que le muestra el uso: http://jsfiddle.net/4tkj8/1/

Armin
fuente
1
Esto es genial. Agregué la opción para ordenar asc / desc: app.filter ('orderObjectBy', function () {return function (input, attribute, direction) {if (! Angular.isObject (input)) return input; var array = [] ; for (var objectKey en entrada) {array.push (input [objectKey]);} array.sort (función (a, b) {a = parseInt (a [atributo]); b = parseInt (b [atributo]) ; dirección de retorno == 'asc'? a - b: b - a;}); matriz de retorno;}}); En HTML: <tr ng-repeat = "val in list | orderObjectBy: 'prop': 'asc'">
Jazzy
@Armin, ¿y si este es un objeto como una matriz? ie{1:'Example 1', 2:'Example 2', 3:'Example 3', ...}
Eugene
8
Gran respuesta PERO perdimos la clave del objeto de esta manera. Para mantenerlo, simplemente agréguelo creando nuevas filas de matriz en el filtro, por ejemplo, for(var objectKey in input) { input[objectKey]['_key'] = objectKey; array.push(input[objectKey]); }así podemos usar<div ng-repeat="value in object | orderObjectBy:'order'" ng-init="key = value['_key']">
Nicolas Janel
7
Vale la pena señalar que los angularjs hace soporte para el ordenado por una propiedad en una matriz de objetos ahora: ... | orderBy: 'name'.
Wildhoney
2
@Wildhoney Esta pregunta trata sobre ordenar objetos con claves, no matrices que contienen objetos.
Armin
31

Es bastante fácil, hazlo así

$scope.props = [{order:"1"},{order:"5"},{order:"2"}]

ng-repeat="prop in props | orderBy:'order'"
León
fuente
33
Esto no funciona para matrices asociativas, que es de lo que trata la pregunta.
MFB
7

No olvide que parseInt () solo funciona para valores enteros. Para ordenar los valores de cadena, debe intercambiar esto:

array.sort(function(a, b){
  a = parseInt(a[attribute]);
  b = parseInt(b[attribute]);
  return a - b;
});

con este:

array.sort(function(a, b){
  var alc = a[attribute].toLowerCase(),
      blc = b[attribute].toLowerCase();
  return alc > blc ? 1 : alc < blc ? -1 : 0;
});
Eric Steinborn
fuente
6

Como puede ver en el código de angular-JS ( https://github.com/angular/angular.js/blob/master/src/ng/filter/orderBy.js ) ng-repeat no funciona con objetos. Aquí hay un truco con sortFunction.

http://jsfiddle.net/sunnycpp/qaK56/33/

<div ng-app='myApp'>
    <div ng-controller="controller">
    <ul>
        <li ng-repeat="test in testData | orderBy:sortMe()">
            Order = {{test.value.order}} -> Key={{test.key}} Name=:{{test.value.name}}
        </li>
    </ul>
    </div>
</div>

myApp.controller('controller', ['$scope', function ($scope) {

    var testData = {
        a:{name:"CData", order: 2},
        b:{name:"AData", order: 3},
        c:{name:"BData", order: 1}
    };
    $scope.testData = _.map(testData, function(vValue, vKey) {
        return { key:vKey, value:vValue };
    }) ;
    $scope.sortMe = function() {
        return function(object) {
            return object.value.order;
        }
    }
}]);
SunnyShah
fuente
4

de acuerdo con http://docs.angularjs.org/api/ng.filter:orderBy , orderBy ordena una matriz. En su caso, está pasando un objeto, por lo que tendrá que implementar su propia función de clasificación.

o pasar una matriz -

$scope.testData = {
    C: {name:"CData", order: 1},
    B: {name:"BData", order: 2},
    A: {name:"AData", order: 3},
}

echa un vistazo a http://jsfiddle.net/qaK56/

Gal Ben-Haim
fuente
Sé que funciona con matrices. ¿Entonces la solución es escribir mi propia función de clasificación?
PrimosK
Tu jsfiddle! = Tu código que publicaste. Este es el jsfiddle correcto para su ejemplo aquí: jsfiddle.net/qaK56/92
mrzmyr
3

Realmente debería mejorar su estructura JSON para solucionar su problema:

$scope.testData = [
   {name:"CData", order: 1},
   {name:"BData", order: 2},
   {name:"AData", order: 3},
]

Entonces podrías hacer

<li ng-repeat="test in testData | orderBy:order">...</li>

El problema, creo, es que las variables (clave, valor) no están disponibles para el filtro orderBy, y de todos modos no debería almacenar datos en sus claves

Joshua Wooward
fuente
2
dígaselo a Firebase;)
MFB
Hasta donde yo sé, creas los datos en firebase
Joshua Wooward
Si almacena una matriz en Firebase, usa un UID como clave para la matriz. Hay muchas situaciones en las que uno no tiene control sobre la estructura de datos, por lo que su sugerencia puede ser un poco amplia.
MFB
almacenar una serie de qué? Uno siempre tiene control sobre la estructura de los datos. Además, el OP no menciona nada sobre Firebase.
Joshua Wooward
2

Esto es lo que hice y funciona.
Solo utilicé un objeto en cadena.

$scope.thread = [ 
  {
    mostRecent:{text:'hello world',timeStamp:12345678 } 
    allMessages:[]
  }
  {MoreThreads...}
  {etc....}
]

<div ng-repeat="message in thread | orderBy : '-mostRecent.timeStamp'" >

si quisiera ordenar por texto lo haría

orderBy : 'mostRecent.text'
James Harrington
fuente
2

Agregaré mi versión mejorada del filtro que admita la siguiente sintaxis:

ng-repeat="(id, item) in $ctrl.modelData | orderObjectBy:'itemProperty.someOrder':'asc'

app.filter('orderObjectBy', function(){

         function byString(o, s) {
            s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
            s = s.replace(/^\./, '');           // strip a leading dot
            var a = s.split('.');
            for (var i = 0, n = a.length; i < n; ++i) {
                var k = a[i];
                if (k in o) {
                    o = o[k];
                } else {
                    return;
                }
            }
            return o;
        }

        return function(input, attribute, direction) {
            if (!angular.isObject(input)) return input;

            var array = [];
            for(var objectKey in input) {
                if (input.hasOwnProperty(objectKey)) {
                    array.push(input[objectKey]);
                }
            }

            array.sort(function(a, b){
                a = parseInt(byString(a, attribute));
                b = parseInt(byString(b, attribute));
                return direction == 'asc' ? a - b : b - a;
            });
            return array;
        }
    })

Gracias a Armin y Jason por sus respuestas en este hilo, y a Alnitak en este hilo .

TachikomaGT
fuente
1

La respuesta de Armin + una verificación estricta de tipos de objetos y claves no angulares como $resolve

app.filter('orderObjectBy', function(){
 return function(input, attribute) {
    if (!angular.isObject(input)) return input;

    var array = [];
    for(var objectKey in input) {
      if (typeof(input[objectKey])  === "object" && objectKey.charAt(0) !== "$")
        array.push(input[objectKey]);
    }

    array.sort(function(a, b){
        a = parseInt(a[attribute]);
        b = parseInt(b[attribute]);
        return a - b;
    });

    return array;
 }
})
momento bipolar
fuente
1

Lo siguiente permite ordenar los objetos por clave O por clave dentro de un objeto .

En la plantilla puedes hacer algo como:

    <li ng-repeat="(k,i) in objectList | orderObjectsBy: 'someKey'">

O incluso:

    <li ng-repeat="(k,i) in objectList | orderObjectsBy: 'someObj.someKey'">

El filtro:

app.filter('orderObjectsBy', function(){
 return function(input, attribute) {
    if (!angular.isObject(input)) return input;

    // Filter out angular objects.
    var array = [];
    for(var objectKey in input) {
      if (typeof(input[objectKey])  === "object" && objectKey.charAt(0) !== "$")
        array.push(input[objectKey]);
    }

    var attributeChain = attribute.split(".");

    array.sort(function(a, b){

      for (var i=0; i < attributeChain.length; i++) {
        a = (typeof(a) === "object") && a.hasOwnProperty( attributeChain[i]) ? a[attributeChain[i]] : 0;
        b = (typeof(b) === "object") && b.hasOwnProperty( attributeChain[i]) ? b[attributeChain[i]] : 0;
      }

      return parseInt(a) - parseInt(b);
    });

    return array;
 }
})
momento bipolar
fuente
¿Es normal que las claves de (k, i) en objectList se transformen en ceros y unos?
Ken Vernaillen