So lassen Sie ng-repeat doppelte Ergebnisse herausfiltern


131

Ich führe eine einfache ng-repeatJSON-Datei aus und möchte Kategorienamen erhalten. Es gibt ungefähr 100 Objekte, die jeweils zu einer Kategorie gehören - aber es gibt nur ungefähr 6 Kategorien.

Mein aktueller Code lautet:

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

Die Ausgabe umfasst 100 verschiedene Optionen, meist Duplikate. Wie überprüfe ich mit Angular, ob eine {{place.category}}bereits vorhanden ist, und erstelle keine Option, wenn sie bereits vorhanden ist?

bearbeiten: In meinem Javascript $scope.places = JSON data, nur um zu verdeutlichen


1
Warum widmen Sie nicht einfach Ihre $ scope.places? benutze jquery map api.jquery.com/map
anazimok

Was war Ihre endgültige Arbeitslösung? IST, dass es oben ist oder gibt es einige JS, die das Duping machen
mark1234

Ich möchte die Lösung dafür wissen. Bitte posten Sie ein Follow-up. Vielen Dank!
jdstein1

@ jdstein1 Ich beginne mit einem TLDR: Verwenden Sie die folgenden Antworten oder verwenden Sie Vanille-Javascript, um nur eindeutige Werte in einem Array herauszufiltern. Was ich getan habe: Am Ende war es ein Problem mit meiner Logik und meinem Verständnis von MVC. Ich habe Daten aus MongoDB geladen und nach einem Speicherauszug gefragt, und Angular wollte, dass diese auf magische Weise auf eindeutige Stellen heruntergefiltert werden. Die Lösung bestand, wie so oft, darin, nicht mehr faul zu sein und mein DB-Modell zu reparieren - für mich war es Mongo's db.collection.distinct("places"), was weitaus besser war, als es in Angular zu tun! Leider funktioniert das nicht bei allen.
JVG

Danke für das Update!
jdstein1

Antworten:


142

Sie können den eindeutigen Filter von AngularUI (Quellcode hier verfügbar: AngularUI-eindeutiger Filter ) verwenden und ihn direkt in den ng-Optionen (oder ng-repeat) verwenden.

<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
Für diejenigen, die den eindeutigen Filter von AngularUI nicht zum Laufen bringen können: Die Filter befinden sich in einem separaten Modul: Sie müssen ihn beispielsweise als zusätzliche Referenz in Ihr Modul aufnehmen angular.module('yourModule', ['ui', 'ui.filters']);. War ratlos, bis ich einen Blick in die AngularUI js-Datei warf.
GFoley83

8
Der uniqueFilter kann derzeit als Teil von AngularJs UI Utils
Nick

2
In der neuen Version von ui utils können Sie ui.unique einfach selbst einbinden. Verwenden Sie die Laubeninstallation nur für das Modul.
Beispiel

Wenn Sie AngularUI und seine Gesamtheit nicht einschließen möchten, aber den eindeutigen Filter verwenden möchten, können Sie die Quelle unique.js einfach kopieren, in Ihre App einfügen und dann angular.module('ui.filters')in Ihren App-Namen ändern .
Chakeda

37

Oder Sie können Ihren eigenen Filter mit lodash schreiben.

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

Hallo Mike, sieht sehr elegant aus, scheint aber nicht wie erwartet zu funktionieren, wenn ich einen Filter wie unique übergebe: status [mein Feldname] gibt nur das erste Ergebnis zurück, im Gegensatz zur gewünschten Liste aller verfügbaren eindeutigen Statuen. Irgendwelche Vermutungen warum?
SinSync

3
Obwohl ich Unterstriche verwendet habe, hat diese Lösung wie ein Zauber funktioniert. Vielen Dank!
Jon Black

Sehr elegante Lösung.
JD Smith

1
lodash ist eine Gabel des Unterstrichs. Es hat eine bessere Leistung und mehr Dienstprogramme.
Mike Ward

2
in lodash v4 _.uniqBy(arr, field);sollte für verschachtelte Eigenschaften
funktionieren

30

Sie können den Filter 'unique' (Aliase: uniq) im Modul angle.filter verwenden

Verwendung: colection | uniq: 'property'
Sie können auch nach verschachtelten Eigenschaften filtern: colection | uniq: 'property.nested_property'

Was Sie tun können, ist so etwas ..

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: Wir filtern nach Kunden-ID, dh entfernen doppelte Kunden

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

Ergebnis
Kundenliste:
foo 10
bar 20
baz 30


Ich mag deine Antwort, aber es fällt mir schwer, sie in mein Problem zu übersetzen. Wie würden Sie mit einer verschachtelten Liste umgehen? Zum Beispiel eine Liste von Bestellungen, die eine Liste von Artikeln für jede Bestellung enthielt. In Ihrem Beispiel haben Sie einen Kunden pro Bestellung. Ich möchte eine eindeutige Liste von Artikeln über alle Bestellungen hinweg sehen. Nicht um den Punkt zu beleuchten, aber Sie können sich auch vorstellen, dass ein Kunde viele Bestellungen und eine Bestellung viele Artikel hat. Wie kann ich alle vorherigen Artikel anzeigen, die ein Kunde bestellt hat? Gedanken?
Greg Grater

@GregGrater Können Sie ein Beispielobjekt bereitstellen, das Ihrem Problem ähnelt?
a8m

Danke @Ariel M.! Hier ist ein Beispiel für die Daten:
Greg Grater

Mit welchen Winkelversionen ist es kompatibel?
Icet

15

Dieser Code funktioniert für mich.

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

und dann

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

2
Die Definition von l sollte l = arr! = Undefiniert sein? arr.length: 0 da sonst ein Analysefehler in anglejs vorliegt
Gerrit

6

Wenn Sie Kategorien auflisten möchten, sollten Sie Ihre Absicht in der Ansicht explizit angeben.

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

in der Steuerung:

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

Könntest du das etwas näher erläutern? Ich möchte die Elemente in einer Reihe für jede einzelne Kategorie wiederholen. Geht der JS nur in die JS-Datei, in der der Controller definiert ist?
Mark1234

Wenn Sie die Optionen gruppieren möchten, ist dies eine andere Frage. Möglicherweise möchten Sie sich das optgroup-Element ansehen. Angular unterstützt Optgroup. Suche nach group byAusdruck in selectDirektive.
Tosh

Wird es passieren, wenn ein Ort hinzugefügt / entfernt wird? Wird die zugehörige Kategorie hinzugefügt / entfernt, wenn dies die einzige Instanz an bestimmten Stellen ist?
Greg Grater

4

Hier ist ein einfaches und allgemeines Beispiel.

Der Filter:

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

Das Markup:

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

Dies funktioniert gut, aber es wirft einen Fehler in der Konsole Cannot read property 'length' of undefinedin der Zeilel = arr.length
Soul Eeater

4

Ich beschloss, die Antwort von @ thethakuri zu erweitern, um dem einzigartigen Mitglied jede Tiefe zu geben. Hier ist der Code. Dies ist für diejenigen gedacht, die nicht das gesamte AngularUI-Modul nur für diese Funktionalität einbeziehen möchten. Wenn Sie AngularUI bereits verwenden, ignorieren Sie diese Antwort:

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

Beispiel

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

2

Ich hatte eine Reihe von Zeichenfolgen, keine Objekte, und ich habe diesen Ansatz verwendet:

ng-repeat="name in names | unique"

mit diesem Filter:

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

AKTUALISIEREN

Ich habe die Verwendung von Set empfohlen, aber leider funktioniert dies weder für ng-repeat noch für Map, da ng-repeat nur mit Array funktioniert. Ignorieren Sie diese Antwort. Wenn Sie Duplikate auf die eine oder andere Weise herausfiltern müssen angular filters, finden Sie hier den Link zum Abschnitt " Erste Schritte" .


Alte Antwort

Sie können die ECMAScript 2015 (ES6) -Standard-Set-Datenstruktur anstelle einer Array-Datenstruktur verwenden, um wiederholte Werte beim Hinzufügen zum Set zu filtern. (Denken Sie daran, dass Sätze keine wiederholten Werte zulassen.) Wirklich einfach zu bedienen:

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

Hier ist eine Möglichkeit nur für Vorlagen (die Reihenfolge wird jedoch nicht beibehalten). Außerdem wird auch das Ergebnis bestellt, was in den meisten Fällen nützlich ist:

<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

Keiner der oben genannten Filter hat mein Problem behoben, sodass ich den Filter aus dem offiziellen Github-Dokument kopieren musste. Und dann verwenden Sie es wie in den obigen Antworten erläutert

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

Rückgabefunktion (items, 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

Es scheint, dass jeder seine eigene Version des uniqueFilters in den Ring wirft , also werde ich das Gleiche tun. Kritik ist sehr willkommen.

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

Wenn Sie eindeutige Daten basierend auf dem verschachtelten Schlüssel erhalten möchten:

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

Nennen Sie es so:

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

0

Fügen Sie diesen Filter hinzu:

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

Aktualisieren Sie Ihr Markup:

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

0

Das mag übertrieben sein, aber es funktioniert bei mir.

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

Die eindeutige Funktion hängt von der oben definierten enthaltenen Funktion ab. Es kann so genannt werden, als array.distinct(prop);ob prop die Eigenschaft ist, die Sie unterscheiden möchten.

Man könnte es also einfach sagen $scope.places.distinct("category");


0

Erstellen Sie Ihr eigenes 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);
    }
});
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.