Jak sprawić, by powtórzenie ng odfiltrować zduplikowane wyniki

131

Uruchamiam prosty ng-repeatplik JSON i chcę uzyskać nazwy kategorii. Istnieje około 100 obiektów, każdy należący do kategorii - ale jest tylko około 6 kategorii.

Mój obecny kod to:

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

Rezultatem jest 100 różnych opcji, głównie duplikatów. Jak użyć Angulara, aby sprawdzić, czy {{place.category}}już istnieje, i nie tworzyć opcji, jeśli już istnieje?

edytuj: W moim javascript $scope.places = JSON data, tylko dla wyjaśnienia

JVG
źródło
1
Dlaczego po prostu nie usuniesz swoich $ scope.places? użyj mapy jquery api.jquery.com/map
anazimok
jakie było Twoje ostateczne rozwiązanie robocze? Czy to jest powyżej, czy też jakiś JS dokonuje de-
dupingu
Chcę poznać rozwiązanie tego problemu. Prosimy o przesłanie dalszych informacji. Dzięki!
jdstein1
@ jdstein1 Zacznę od TLDR: skorzystaj z poniższych odpowiedzi lub użyj prostego kodu JavaScript, aby odfiltrować tylko unikalne wartości w tablicy. Co zrobiłem: w końcu był to problem z moją logiką i moim zrozumieniem MVC. Ładowałem dane z MongoDB, prosząc o zrzut danych i chciałem, aby Angular w magiczny sposób przefiltrował je tylko do unikalnych miejsc. Rozwiązaniem, jak to często bywa, było przestanie być leniwym i naprawienie mojego modelu DB - dla mnie to dzwonienie do Mongo db.collection.distinct("places"), co było o wiele, znacznie lepsze niż robienie tego w Angular! Niestety to nie zadziała dla wszystkich.
JVG,
Dziękuję za aktualizację!
jdstein1

Odpowiedzi:

142

Możesz użyć unikalnego filtra z AngularUI (kod źródłowy dostępny tutaj: unikalny filtr AngularUI ) i użyć go bezpośrednio w ng-options (lub 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
źródło
33
Dla tych, którzy nie mogą uzyskać unikalnego filtra działającego AngularUI: filtry są w osobnym module: Np. Musisz dołączyć go jako dodatkowe odniesienie do swojego modułu angular.module('yourModule', ['ui', 'ui.filters']);. Byłem zaskoczony, dopóki nie zajrzałem do pliku js AngularUI.
GFoley83
8
uniqueFiltr można obecnie znaleźć w ramach angularjs UI Utils
Nick
2
W nowej wersji narzędzi ui możesz po prostu dołączyć własny ui.unique. użyj instalacji bower tylko dla modułu.
Statystyki uczenia się na przykładzie
Jeśli nie chcesz dołączać AngularUI i jego całości, ale chcesz użyć unikalnego filtru, możesz po prostu skopiować i wkleić źródło unique.js do swojej aplikacji, a następnie zmienić angular.module('ui.filters')nazwę aplikacji.
chakeda
37

Możesz też napisać własny filtr za pomocą lodash.

app.filter('unique', function() {
    return function (arr, field) {
        return _.uniq(arr, function(a) { return a[field]; });
    };
});
Mike Ward
źródło
Cześć Mike, wygląda naprawdę elegancko, ale wydaje się nie działać zgodnie z oczekiwaniami, kiedy przepuszczam filtr, taki jak unique: status [nazwa mojego pola], zwraca tylko pierwszy wynik, a nie żądaną listę wszystkich dostępnych unikalnych statuetek. Jakieś przypuszczenia, dlaczego?
SinSync
3
Chociaż użyłem podkreślenia, to rozwiązanie zadziałało jak urok. Dzięki!
Jon Black,
Bardzo eleganckie rozwiązanie.
JD Smith
1
lodash to widelec podkreślenia. Ma lepszą wydajność i więcej narzędzi.
Mike Ward
2
w lodash v4 _.uniqBy(arr, field);powinno działać dla zagnieżdżonych właściwości
ijavid
30

Możesz użyć filtru „unique” (aliasy: uniq) w module angular.filter

użycie: colection | uniq: 'property'
możesz również filtrować według zagnieżdżonych właściwości: colection | uniq: 'property.nested_property'

To, co możesz zrobić, jest takie ...

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: filtrujemy według identyfikatora klienta, tj. Usuwamy zduplikowanych klientów

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

wynik
Lista klientów:
foo 10
bar 20
baz 30

a8m
źródło
podoba mi się twoja odpowiedź, ale trudno mi przełożyć ją na mój problem. Jak poradzisz sobie z zagnieżdżoną listą. Na przykład lista zamówień zawierająca listę towarów dla każdego zamówienia. W Twoim przykładzie masz jednego klienta na zamówienie. Chciałbym zobaczyć unikalną listę pozycji we wszystkich zamówieniach. Nie żeby rozwodzić się nad tym zagadnieniem, można też myśleć o tym, że klient ma wiele zamówień, a zamówienie składa się z wielu pozycji. Jak mogę wyświetlić wszystkie poprzednie pozycje zamówione przez klienta? Myśli?
Greg Grater
@GregGrater Czy możesz podać przykładowy obiekt podobny do Twojego problemu?
a8m
Dzięki @Ariel M.! Oto przykład danych:
Greg Grater
Z jakimi wersjami kątownika jest kompatybilny?
Icet
15

ten kod działa dla mnie.

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

i wtedy

var colors=$filter('unique')(items,"color");
Eduardo Ortiz
źródło
2
Definicja l powinna być l = arr! = Undefined? arr.length: 0 ponieważ w przeciwnym razie wystąpił błąd analizy w angularjs
Gerrit
6

Jeśli chcesz wymienić kategorie, myślę, że powinieneś wyraźnie określić swój zamiar w widoku.

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

w kontrolerze:

$scope.categories = $scope.places.reduce(function(sum, place) {
  if (sum.indexOf( place.category ) < 0) sum.push( place.category );
  return sum;
}, []);
Bzdury
źródło
czy mógłbyś to trochę wyjaśnić. Chcę powtórzyć te elementy z rzędu dla każdej unikalnej kategorii. Czy JS po prostu trafia do pliku JS, w którym zdefiniowano kontroler?
mark1234
Jeśli chcesz pogrupować opcje, to jest inne pytanie. Możesz zajrzeć do elementu optgroup. Angular obsługuje optgroup. szukaj group bywyrażenia w selectdyrektywie.
Tosh
Czy nastąpi to w przypadku dodania / usunięcia miejsca? Czy powiązana kategoria zostanie dodana / usunięta, jeśli jest to jedyna instancja w niektórych miejscach?
Greg Grater
4

Oto prosty i ogólny przykład.

Filtr:

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

Znaczniki:

<div ng-repeat = "item in items | unique:'name'">
  {{ item.name }}
</div>
<script src="your/filters.js"></script>
Erik Trautman
źródło
To działa dobrze, ale generuje błąd w konsoli Cannot read property 'length' of undefinedw liniil = arr.length
Soul Eeater,
4

Postanowiłem rozszerzyć odpowiedź @ thethakuri, aby pozwolić na jakąkolwiek głębię dla unikalnego członka. Oto kod. To jest dla tych, którzy nie chcą dołączać całego modułu AngularUI tylko dla tej funkcjonalności. Jeśli już używasz AngularUI, zignoruj ​​tę odpowiedź:

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

Przykład

<div ng-repeat="item in items | unique : 'subitem.subitem.subitem.value'"></div>
kennasoft
źródło
2

Miałem tablicę ciągów znaków, a nie obiektów i zastosowałem następujące podejście:

ng-repeat="name in names | unique"

z tym filtrem:

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

  };
}
Post Impatica
źródło
2

AKTUALIZACJA

Polecałem użycie Set, ale przepraszam, że to nie działa dla ng-repeat ani Map, ponieważ ng-repeat działa tylko z array. Więc zignoruj ​​tę odpowiedź. w każdym razie, jeśli musisz odfiltrować duplikaty w jeden sposób, tak jak powiedział inny, używając angular filters, tutaj jest link do sekcji z wprowadzeniem .


Stara odpowiedź

Możesz użyć standardowej struktury danych zestawu ECMAScript 2015 (ES6) zamiast struktury danych tablicowych, w ten sposób filtrujesz powtarzające się wartości podczas dodawania do zestawu. (Pamiętaj, że zestawy nie pozwalają na powtarzanie wartości). Naprawdę łatwy w użyciu:

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
źródło
2

Wygląda na to, że każdy wrzuca uniquedo pierścienia własną wersję filtra, więc zrobię to samo. Krytyka jest bardzo mile widziana.

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]];
      });
    };
  });
Pan Lance E Sloan
źródło
2

Oto sposób, w jaki można to zrobić tylko za pomocą szablonu (nie jest to jednak utrzymanie kolejności). Ponadto wynik zostanie również uporządkowany, co jest przydatne w większości przypadków:

<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>
buty
źródło
2

Żaden z powyższych filtrów nie rozwiązał mojego problemu, więc musiałem skopiować filtr z oficjalnego dokumentu github. A następnie użyj go, jak wyjaśniono w powyższych odpowiedziach

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

return function (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;
  };

});
Czarna Mamba
źródło
1

Jeśli chcesz uzyskać unikalne dane na podstawie zagnieżdżonego klucza:

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

Nazwij to tak:

<div ng-repeat="notify in notifications | unique: 'firstlevel':'secondlevel'">
thethakuri
źródło
0

Dodaj ten filtr:

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

Zaktualizuj swoje znaczniki:

<select ng-model="orderProp" >
   <option ng-repeat="place in places | unique" value="{{place.category}}">{{place.category}}</option>
</select>
Shawn Dotey
źródło
0

To może być przesada, ale dla mnie działa.

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

Odrębna funkcja zależy od funkcji zawiera zdefiniowanej powyżej. Można to nazwać tak, że array.distinct(prop);gdzie prop jest właściwością, którą chcesz wyróżnić.

Więc możesz po prostu powiedzieć $scope.places.distinct("category");

Ikechi Michael
źródło
0

Utwórz własną tablicę.

<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
źródło