Comment faire en sorte que ng-repeat filtre les résultats en double

131

J'exécute un simple ng-repeatsur un fichier JSON et je souhaite obtenir des noms de catégories. Il y a environ 100 objets, chacun appartenant à une catégorie - mais il n'y a qu'environ 6 catégories.

Mon code actuel est le suivant:

<select ng-model="orderProp" >
  <option ng-repeat="place in places" value="{{place.category}}">{{place.category}}</option>
</select>

La sortie est 100 options différentes, pour la plupart des doublons. Comment utiliser Angular pour vérifier si un {{place.category}}existe déjà et ne pas créer d'option s'il existe déjà?

edit: Dans mon javascript $scope.places = JSON data, juste pour clarifier

JVG
la source
1
Pourquoi ne déduisez-vous pas simplement votre $ scope.places? utiliser jquery map api.jquery.com/map
anazimok
quelle a été votre solution de travail finale? Est-ce que c'est au-dessus ou y a-t-il des JS qui font la
déduplication
Je veux connaître la solution à cela. Veuillez poster un suivi. Merci!
jdstein1
@ jdstein1 Je vais commencer par un TLDR: utilisez les réponses ci-dessous, ou utilisez du JavaScript vanille pour filtrer uniquement les valeurs uniques dans un tableau. Ce que j'ai fait: En fin de compte, c'était un problème avec ma logique et ma compréhension de MVC. Je chargeais des données de MongoDB pour demander un vidage de données et je voulais qu'Angular les filtre comme par magie vers des endroits uniques. La solution, comme si souvent le cas, était d'arrêter d'être paresseux et de réparer mon modèle de base de données - pour moi, cela appelait Mongo's db.collection.distinct("places"), ce qui était bien, bien mieux que de le faire dans Angular! Malheureusement, cela ne fonctionnera pas pour tout le monde.
JVG
Merci pour la mise à jour!
jdstein1

Réponses:

142

Vous pouvez utiliser le filtre unique d'AngularUI (code source disponible ici: AngularUI unique filter ) et l'utiliser directement dans les ng-options (ou ng-repeat).

<select ng-model="orderProp" ng-options="place.category for place in places | unique:'category'">
    <option value="0">Default</option>
    // unique options from the categories
</select>
jpmorin
la source
33
Pour ceux qui ne peuvent pas faire fonctionner le filtre unique d'AngularUI: les filtres sont dans un module séparé: par exemple, vous devez l'inclure comme référence supplémentaire dans votre module angular.module('yourModule', ['ui', 'ui.filters']);. J'étais perplexe jusqu'à ce que je jette un coup d'œil dans le fichier js AngularUI.
GFoley83
8
Le uniquefiltre peut actuellement être trouvé dans le cadre de AngularJs UI Utils
Nick
2
Dans la nouvelle version de ui utils, vous pouvez simplement inclure ui.unique seul. utilisez l'installation de bower uniquement pour le module.
Apprentissage des statistiques par exemple le
Si vous ne souhaitez pas inclure AngularUI et son intégralité, mais que vous souhaitez utiliser le filtre unique, vous pouvez simplement copier-coller la source unique.js dans votre application, puis changer angular.module('ui.filters')le nom de votre application.
chakeda
37

Ou vous pouvez écrire votre propre filtre en utilisant lodash.

app.filter('unique', function() {
    return function (arr, field) {
        return _.uniq(arr, function(a) { return a[field]; });
    };
});
Mike Ward
la source
Bonjour Mike, a l'air vraiment élégant mais ne semble pas fonctionner comme prévu lorsque je passe un filtre comme unique: status [mon nom de champ] il ne renvoie que le premier résultat par opposition à la liste souhaitée de toutes les statues uniques disponibles. Des suppositions pourquoi?
SinSync
3
Bien que j'utilise un trait de soulignement, cette solution a fonctionné comme un charme. Merci!
Jon Black
Solution très élégante.
JD Smith
1
lodash est une fourchette de soulignement. Il a de meilleures performances et plus d'utilitaires.
Mike Ward
2
dans lodash v4 _.uniqBy(arr, field);devrait fonctionner pour les propriétés imbriquées
ijavid
30

Vous pouvez utiliser le filtre 'unique' (alias: uniq) dans le module angular.filter

utilisation: colection | uniq: 'property'
vous pouvez également filtrer par propriétés imbriquées: colection | uniq: 'property.nested_property'

Ce que vous pouvez faire, c'est quelque chose comme ça ...

function MainController ($scope) {
 $scope.orders = [
  { id:1, customer: { name: 'foo', id: 10 } },
  { id:2, customer: { name: 'bar', id: 20 } },
  { id:3, customer: { name: 'foo', id: 10 } },
  { id:4, customer: { name: 'bar', id: 20 } },
  { id:5, customer: { name: 'baz', id: 30 } },
 ];
}

HTML: nous filtrons par identifiant client, c'est-à-dire supprimons les clients en double

<th>Customer list: </th>
<tr ng-repeat="order in orders | unique: 'customer.id'" >
   <td> {{ order.customer.name }} , {{ order.customer.id }} </td>
</tr>

résultat
Liste des clients:
foo 10
bar 20
baz 30

a8m
la source
comme votre réponse, mais ayant du mal à la traduire à mon problème. Comment géreriez-vous une liste imbriquée. Par exemple, une liste de commandes contenant une liste d'articles pour chaque commande. Dans votre exemple, vous avez un client par commande. J'aimerais voir une liste unique d'articles pour toutes les commandes. Ne pas insister sur ce point, mais vous pouvez aussi y penser comme un client a de nombreuses commandes et une commande a de nombreux articles. Comment puis-je afficher tous les articles précédents qu'un client a commandés? Pensées?
Greg Grater
@GregGrater Pouvez-vous fournir un exemple d'objet similaire à votre problème?
a8m
Merci @Ariel M.! Voici un exemple des données:
Greg Grater
Avec quelles versions d'angular est-il compatible?
Icet
15

ce code fonctionne pour moi.

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

  return function (arr, field) {
    var o = {}, i, l = arr.length, r = [];
    for(i=0; i<l;i+=1) {
      o[arr[i][field]] = arr[i];
    }
    for(i in o) {
      r.push(o[i]);
    }
    return r;
  };
})

puis

var colors=$filter('unique')(items,"color");
Eduardo Ortiz
la source
2
La définition de l devrait être l = arr! = Undefined? arr.length: 0 car sinon il y a une erreur d'analyse dans angularjs
Gerrit
6

Si vous souhaitez lister des catégories, je pense que vous devez explicitement indiquer votre intention dans la vue.

<select ng-model="orderProp" >
  <option ng-repeat="category in categories"
          value="{{category}}">
    {{category}}
  </option>
</select>

dans le contrôleur:

$scope.categories = $scope.places.reduce(function(sum, place) {
  if (sum.indexOf( place.category ) < 0) sum.push( place.category );
  return sum;
}, []);
Tosh
la source
pourriez-vous expliquer cela un peu plus. Je veux répéter les éléments dans une rangée pour chaque catégorie unique. Le JS va-t-il simplement dans le fichier JS où le contrôleur est défini?
mark1234
Si vous souhaitez regrouper les options, c'est une question différente. Vous voudrez peut-être examiner l'élément optgroup. Angular prend en charge optgroup. recherche d' group byexpression dans la selectdirective.
Tosh
Cela se produira-t-il dans le cas où un lieu est ajouté / supprimé? La catégorie associée sera-t-elle ajoutée / supprimée s'il s'agit de la seule instance à certains endroits?
Greg Grater
4

Voici un exemple simple et générique.

Le filtre:

sampleApp.filter('unique', function() {

  // Take in the collection and which field
  //   should be unique
  // We assume an array of objects here
  // NOTE: We are skipping any object which
  //   contains a duplicated value for that
  //   particular key.  Make sure this is what
  //   you want!
  return function (arr, targetField) {

    var values = [],
        i, 
        unique,
        l = arr.length, 
        results = [],
        obj;

    // Iterate over all objects in the array
    // and collect all unique values
    for( i = 0; i < arr.length; i++ ) {

      obj = arr[i];

      // check for uniqueness
      unique = true;
      for( v = 0; v < values.length; v++ ){
        if( obj[targetField] == values[v] ){
          unique = false;
        }
      }

      // If this is indeed unique, add its
      //   value to our values and push
      //   it onto the returned array
      if( unique ){
        values.push( obj[targetField] );
        results.push( obj );
      }

    }
    return results;
  };
})

Le balisage:

<div ng-repeat = "item in items | unique:'name'">
  {{ item.name }}
</div>
<script src="your/filters.js"></script>
Erik Trautman
la source
Cela fonctionne bien mais cela jette une erreur dans la console Cannot read property 'length' of undefineddans la lignel = arr.length
Soul Eeater
4

J'ai décidé d'étendre la réponse de @ thethakuri pour autoriser n'importe quelle profondeur pour le membre unique. Voici le code. C'est pour ceux qui ne veulent pas inclure tout le module AngularUI uniquement pour cette fonctionnalité. Si vous utilisez déjà AngularUI, ignorez cette réponse:

app.filter('unique', function() {
    return function(collection, primaryKey) { //no need for secondary key
      var output = [], 
          keys = [];
          var splitKeys = primaryKey.split('.'); //split by period


      angular.forEach(collection, function(item) {
            var key = {};
            angular.copy(item, key);
            for(var i=0; i<splitKeys.length; i++){
                key = key[splitKeys[i]];    //the beauty of loosely typed js :)
            }

            if(keys.indexOf(key) === -1) {
              keys.push(key);
              output.push(item);
            }
      });

      return output;
    };
});

Exemple

<div ng-repeat="item in items | unique : 'subitem.subitem.subitem.value'"></div>
Kennasoft
la source
2

J'avais un tableau de chaînes, pas d'objets et j'ai utilisé cette approche:

ng-repeat="name in names | unique"

avec ce filtre:

angular.module('app').filter('unique', unique);
function unique(){
return function(arry){
        Array.prototype.getUnique = function(){
        var u = {}, a = [];
        for(var i = 0, l = this.length; i < l; ++i){
           if(u.hasOwnProperty(this[i])) {
              continue;
           }
           a.push(this[i]);
           u[this[i]] = 1;
        }
        return a;
    };
    if(arry === undefined || arry.length === 0){
          return '';
    }
    else {
         return arry.getUnique(); 
    }

  };
}
Helzgate
la source
2

METTRE À JOUR

Je recommandais l'utilisation de Set mais désolé cela ne fonctionne pas pour ng-repeat, ni pour Map puisque ng-repeat ne fonctionne qu'avec array. Alors ignorez cette réponse. Quoi qu'il en soit, si vous avez besoin de filtrer les doublons dans un sens, comme l'a dit l'autre angular filters, voici le lien pour cela vers la section de démarrage .


Ancienne réponse

Vous pouvez utiliser la structure de données d'ensemble standard ECMAScript 2015 (ES6) , au lieu d'une structure de données de tableau, de cette façon, vous filtrez les valeurs répétées lors de l'ajout à l'ensemble. (Rappelez-vous que les ensembles ne permettent pas de valeurs répétées). Vraiment facile à utiliser:

var mySet = new Set();

mySet.add(1);
mySet.add(5);
mySet.add("some text");
var o = {a: 1, b: 2};
mySet.add(o);

mySet.has(1); // true
mySet.has(3); // false, 3 has not been added to the set
mySet.has(5);              // true
mySet.has(Math.sqrt(25));  // true
mySet.has("Some Text".toLowerCase()); // true
mySet.has(o); // true

mySet.size; // 4

mySet.delete(5); // removes 5 from the set
mySet.has(5);    // false, 5 has been removed

mySet.size; // 3, we just removed one value
CommonSenseCode
la source
2

Voici une façon de le faire uniquement par modèle (cependant, il ne s'agit pas de maintenir l'ordre). De plus, le résultat sera également ordonné, ce qui est utile dans la plupart des cas:

<select ng-model="orderProp" >
   <option ng-repeat="place in places | orderBy:'category' as sortedPlaces" data-ng-if="sortedPlaces[$index-1].category != place.category" value="{{place.category}}">
      {{place.category}}
   </option>
</select>
chaussures
la source
2

Aucun des filtres ci-dessus n'a résolu mon problème, j'ai donc dû copier le filtre de la documentation officielle de github. Et puis utilisez-le comme expliqué dans les réponses ci-dessus

angular.module('yourAppNameHere').filter('unique', function () {

fonction de retour (éléments, filterOn) {

if (filterOn === false) {
  return items;
}

if ((filterOn || angular.isUndefined(filterOn)) && angular.isArray(items)) {
  var hashCheck = {}, newItems = [];

  var extractValueToCompare = function (item) {
    if (angular.isObject(item) && angular.isString(filterOn)) {
      return item[filterOn];
    } else {
      return item;
    }
  };

  angular.forEach(items, function (item) {
    var valueToCheck, isDuplicate = false;

    for (var i = 0; i < newItems.length; i++) {
      if (angular.equals(extractValueToCompare(newItems[i]), extractValueToCompare(item))) {
        isDuplicate = true;
        break;
      }
    }
    if (!isDuplicate) {
      newItems.push(item);
    }

  });
  items = newItems;
}
return items;
  };

});
Black Mamba
la source
1

Il semble que tout le monde jette sa propre version du uniquefiltre sur le ring, alors je vais faire de même. La critique est la bienvenue.

angular.module('myFilters', [])
  .filter('unique', function () {
    return function (items, attr) {
      var seen = {};
      return items.filter(function (item) {
        return (angular.isUndefined(attr) || !item.hasOwnProperty(attr))
          ? true
          : seen[item[attr]] = !seen[item[attr]];
      });
    };
  });
LS
la source
1

Si vous souhaitez obtenir des données uniques basées sur la clé imbriquée:

app.filter('unique', function() {
        return function(collection, primaryKey, secondaryKey) { //optional secondary key
          var output = [], 
              keys = [];

          angular.forEach(collection, function(item) {
                var key;
                secondaryKey === undefined ? key = item[primaryKey] : key = item[primaryKey][secondaryKey];

                if(keys.indexOf(key) === -1) {
                  keys.push(key);
                  output.push(item);
                }
          });

          return output;
        };
    });

Appelez ça comme ça:

<div ng-repeat="notify in notifications | unique: 'firstlevel':'secondlevel'">
thethakuri
la source
0

Ajoutez ce filtre:

app.filter('unique', function () {
return function ( collection, keyname) {
var output = [],
    keys = []
    found = [];

if (!keyname) {

    angular.forEach(collection, function (row) {
        var is_found = false;
        angular.forEach(found, function (foundRow) {

            if (foundRow == row) {
                is_found = true;                            
            }
        });

        if (is_found) { return; }
        found.push(row);
        output.push(row);

    });
}
else {

    angular.forEach(collection, function (row) {
        var item = row[keyname];
        if (item === null || item === undefined) return;
        if (keys.indexOf(item) === -1) {
            keys.push(item);
            output.push(row);
        }
    });
}

return output;
};
});

Mettez à jour votre balisage:

<select ng-model="orderProp" >
   <option ng-repeat="place in places | unique" value="{{place.category}}">{{place.category}}</option>
</select>
Shawn Dotey
la source
0

C'est peut-être exagéré, mais cela fonctionne pour moi.

Array.prototype.contains = function (item, prop) {
var arr = this.valueOf();
if (prop == undefined || prop == null) {
    for (var i = 0; i < arr.length; i++) {
        if (arr[i] == item) {
            return true;
        }
    }
}
else {
    for (var i = 0; i < arr.length; i++) {
        if (arr[i][prop] == item) return true;
    }
}
return false;
}

Array.prototype.distinct = function (prop) {
   var arr = this.valueOf();
   var ret = [];
   for (var i = 0; i < arr.length; i++) {
       if (!ret.contains(arr[i][prop], prop)) {
           ret.push(arr[i]);
       }
   }
   arr = [];
   arr = ret;
   return arr;
}

La fonction distincte dépend de la fonction contient définie ci-dessus. Il peut être appelé comme array.distinct(prop);où prop est la propriété que vous souhaitez distinguer.

Alors tu pourrais juste dire $scope.places.distinct("category");

Ikechi Michael
la source
0

Créez votre propre tableau.

<select name="cmpPro" ng-model="test3.Product" ng-options="q for q in productArray track by q">
    <option value="" >Plans</option>
</select>

 productArray =[];
angular.forEach($scope.leadDetail, function(value,key){
    var index = $scope.productArray.indexOf(value.Product);
    if(index === -1)
    {
        $scope.productArray.push(value.Product);
    }
});
Akhilesh Kumar
la source