Come fare in modo che ng-repeat elimini i risultati duplicati


131

Sto eseguendo un semplice ng-repeat su un file JSON e voglio ottenere i nomi delle categorie. Ci sono circa 100 oggetti, ognuno appartenente a una categoria - ma ci sono solo circa 6 categorie.

Il mio codice attuale è questo:

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

L'output è di 100 diverse opzioni, per lo più duplicati. Come posso usare Angular per verificare se {{place.category}}esiste già e non creare un'opzione se è già presente?

modifica: Nel mio javascript $scope.places = JSON data, solo per chiarire


1
Perché non dedichi semplicemente $ scope.places? usa la mappa jquery api.jquery.com/map
anazimok il

qual è stata la tua soluzione di lavoro finale? È che sopra o c'è qualche JS che fa il de-
duping

Voglio sapere la soluzione a questo. Si prega di inviare un follow-up. Grazie!
jdstein1

@ jdstein1 Inizierò con un TLDR: utilizzare le risposte di seguito o utilizzare Javascript vanilla per filtrare solo valori univoci in un array. Cosa ho fatto: alla fine, è stato un problema con la mia logica e la mia comprensione di MVC. Stavo caricando i dati da MongoDB chiedendo un dump di dati e volevo che Angular li filtrasse magicamente in posti unici. La soluzione, come spesso accade, era quella di smettere di essere pigro e riparare il mio modello di DB - per me, era chiamare Mongo db.collection.distinct("places"), che era molto, molto meglio che farlo dentro Angular! Purtroppo questo non funzionerà per tutti.
JVG,

grazie per l'aggiornamento!
jdstein1

Risposte:


142

È possibile utilizzare il filtro univoco da AngularUI (codice sorgente disponibile qui: filtro univoco AngularUI ) e utilizzarlo direttamente nelle opzioni ng (o 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>

33
Per coloro che non riescono a far funzionare il filtro univoco di AngularUI: i filtri si trovano in un modulo separato: ad esempio, è necessario includerlo come riferimento aggiuntivo nel modulo angular.module('yourModule', ['ui', 'ui.filters']);. Rimase perplesso finché non diedi un'occhiata al file js di AngularUI.
GFoley83,

8
Il uniquefiltro è attualmente disponibile come parte dell'interfaccia utente
Nick,

2
Nella nuova versione di ui utils, puoi semplicemente includere ui.unique da solo. usa bower install solo per il modulo.
Statistiche di apprendimento con l'esempio

Se non vuoi includere AngularUI e la sua interezza, ma vuoi usare il filtro univoco, puoi semplicemente copiare e incollare l'origine unique.js nella tua app, quindi cambiare angular.module('ui.filters')il nome dell'app.
Chakeda,

37

Oppure puoi scrivere il tuo filtro usando lodash.

app.filter('unique', function() {
    return function (arr, field) {
        return _.uniq(arr, function(a) { return a[field]; });
    };
});

Ciao Mike, sembra davvero elegante ma non sembra funzionare come previsto quando passo un filtro come unico: status [il mio nome campo] restituisce solo il primo risultato in contrapposizione all'elenco desiderato di tutte le statue uniche disponibili. Qualche ipotesi sul perché?
SinSync,

3
Anche se ho usato il trattino basso, questa soluzione ha funzionato come un fascino. Grazie!
Jon Black

Soluzione molto elegante.
JD Smith,

1
lodash è un fork di trattino basso. Ha prestazioni migliori e più utilità.
Mike Ward,

2
in lodash v4 _.uniqBy(arr, field);dovrebbe funzionare con proprietà nidificate
ijavid,

30

È possibile utilizzare il filtro "unico" (alias: uniq) nel modulo angular.filter

utilizzo: colection | uniq: 'property'
puoi anche filtrare per proprietà nidificate: colection | uniq: 'property.nested_property'

Quello che puoi fare è qualcosa del genere ..

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: filtriamo per ID cliente, ovvero rimuoviamo i clienti duplicati

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

risultato
Elenco clienti:
foo 10
bar 20
baz 30


come la tua risposta, ma ho difficoltà a tradurla nel mio problema. Come gestiresti un elenco nidificato. Ad esempio, un elenco di ordini che conteneva un elenco di articoli per ciascun ordine. Nel tuo esempio, hai un cliente per ordine. Vorrei vedere un elenco unico di articoli in tutti gli ordini. Non per ignorare il punto, ma puoi anche pensarlo come un cliente ha molti ordini e un ordine ha molti articoli. Come posso mostrare tutti gli articoli precedenti che un cliente ha ordinato? Pensieri?
Greg Grater,

@GregGrater Puoi fornire un oggetto di esempio simile al tuo problema?
8

Grazie @Ariel M.! Ecco un esempio dei dati:
Greg Grater,

Con quali versioni di angolare è compatibile?
Icet,

15

questo codice funziona per me.

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;
  };
})

e poi

var colors=$filter('unique')(items,"color");

2
La definizione di l dovrebbe essere l = arr! = Undefined? lunghezza arr .: 0 poiché altrimenti si verifica un errore di analisi in angularjs
Gerrit

6

Se vuoi elencare le categorie, penso che dovresti dichiarare esplicitamente la tua intenzione nella vista.

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

nel controller:

$scope.categories = $scope.places.reduce(function(sum, place) {
  if (sum.indexOf( place.category ) < 0) sum.push( place.category );
  return sum;
}, []);

potresti spiegarlo un po 'di più. Voglio ripetere gli elementi di fila per ogni categoria unica. JS va semplicemente nel file JS in cui è definito il controller?
mark1234,

Se vuoi raggruppare le opzioni, è una domanda diversa. Potresti voler esaminare l'elemento optgroup. Supporti angolari optgroup. cerca group byespressione nella selectdirettiva.
Tosh,

Accadrà nel caso in cui un luogo venga aggiunto / rimosso? La categoria associata verrà aggiunta / rimossa se è l'unica istanza in alcuni punti?
Greg Grater,

4

Ecco un esempio semplice e generico.

Il filtro:

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;
  };
})

Il markup:

<div ng-repeat = "item in items | unique:'name'">
  {{ item.name }}
</div>
<script src="your/filters.js"></script>

Funziona bene ma genera un errore nella console Cannot read property 'length' of undefinednella lineal = arr.length
Soul Eeater

4

Ho deciso di estendere la risposta di @ thethakuri per consentire qualsiasi profondità per il membro unico. Ecco il codice. Questo è per coloro che non vogliono includere l'intero modulo AngularUI solo per questa funzionalità. Se stai già utilizzando AngularUI, ignora questa risposta:

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;
    };
});

Esempio

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

2

Avevo una serie di stringhe, non oggetti e ho usato questo approccio:

ng-repeat="name in names | unique"

con questo filtro:

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(); 
    }

  };
}

2

AGGIORNARE

Stavo raccomandando l'uso di Set ma mi dispiace che questo non funzioni per ng-repeat, né Map poiché ng-repeat funziona solo con l'array. Quindi ignora questa risposta. comunque se hai bisogno di filtrare i duplicati in un modo è come altri hanno già detto usando angular filters, ecco il link per farlo alla sezione iniziale .


Vecchia risposta

È possibile utilizzare la struttura Set Data standard ECMAScript 2015 (ES6) , anziché una Struttura dati array in questo modo per filtrare i valori ripetuti quando si aggiunge al Set. (Ricorda che i set non consentono valori ripetuti). Davvero facile da usare:

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

2

Ecco un modo solo modello per farlo (non sta mantenendo l'ordine, però). Inoltre, verrà ordinato anche il risultato, utile nella maggior parte dei casi:

<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>

2

Nessuno dei filtri sopra ha risolto il mio problema, quindi ho dovuto copiare il filtro dal documento ufficiale di Github. E poi usalo come spiegato nelle risposte sopra

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

funzione di ritorno (articoli, 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;
  };

});

1

Sembra che tutti stiano lanciando la propria versione del uniquefiltro sul ring, quindi farò lo stesso. La critica è molto gradita.

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]];
      });
    };
  });

1

Se si desidera ottenere dati univoci basati sulla chiave nidificata:

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;
        };
    });

Chiamalo così:

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

0

Aggiungi questo filtro:

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;
};
});

Aggiorna il tuo markup:

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

0

Questo potrebbe essere eccessivo, ma funziona per me.

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 funzione distinta dipende dalla funzione contiene definita sopra. Può essere chiamato come array.distinct(prop);dove prop è la proprietà che si desidera distinguere.

Quindi potresti solo dire $scope.places.distinct("category");


0

Crea il tuo array.

<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);
    }
});
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.