Как заставить ng-repeat отфильтровать повторяющиеся результаты

131

Я запускаю простой ng-repeatфайл JSON и хочу получить имена категорий. Всего около 100 объектов, каждый из которых относится к определенной категории, но их всего около 6 категорий.

Мой текущий код такой:

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

На выходе получается 100 разных вариантов, в основном дубликаты. Как мне использовать Angular, чтобы проверить, {{place.category}}существует ли уже существующий, и не создавать параметр, если он уже существует?

изменить: в моем javascript, $scope.places = JSON dataчтобы уточнить

JVG
источник
1
Почему бы вам просто не вывести свои $ scope.places? используйте jquery map api.jquery.com/map
anazimok
каким было ваше окончательное рабочее решение? ЯВЛЯЕТСЯ, что это выше, или есть какой-то JS, который
устраняет дублирование
Я хочу знать решение этой проблемы. Пожалуйста, опубликуйте продолжение. Спасибо!
jdstein1
@ jdstein1 Я начну с TL; DR: используйте ответы ниже или используйте обычный Javascript для фильтрации только уникальных значений в массиве. Что я сделал: В конце концов, это была проблема с моей логикой и моим пониманием MVC. Я загружал данные из MongoDB, запрашивая дамп данных, и хотел, чтобы Angular волшебным образом отфильтровал их до уникальных мест. Решение, как это часто бывает, заключалось в том, чтобы перестать лениться и исправить мою модель БД - для меня это было вызовом Mongo db.collection.distinct("places"), что было намного лучше, чем делать это в Angular! К сожалению, это не сработает для всех.
JVG
Спасибо за обновление!
jdstein1 05

Ответы:

142

Вы можете использовать уникальный фильтр из AngularUI (исходный код доступен здесь: уникальный фильтр AngularUI ) и использовать его непосредственно в ng-options (или 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
источник
33
Для тех, кто не может заставить работать уникальный фильтр AngularUI: фильтры находятся в отдельном модуле: например, вы должны включить его в качестве дополнительной ссылки в свой модуль angular.module('yourModule', ['ui', 'ui.filters']);. Был в тупике, пока не заглянул внутрь js файла AngularUI.
GFoley83
8
uniqueФильтр в настоящее время можно найти как часть AngularJs UI Utils
Ник
2
В новой версии ui utils вы можете просто включить ui.unique отдельно. используйте bower install только для модуля.
Статистика обучения на примере
Если вы не хотите включать AngularUI и его целиком, но хотите использовать уникальный фильтр, вы можете просто скопировать и вставить источник unique.js в свое приложение, а затем изменить angular.module('ui.filters')имя приложения.
чакеда
37

Или вы можете написать свой собственный фильтр, используя lodash.

app.filter('unique', function() {
    return function (arr, field) {
        return _.uniq(arr, function(a) { return a[field]; });
    };
});
Майк Уорд
источник
Привет, Майк, выглядит очень элегантно, но, похоже, не работает должным образом, когда я передаю фильтр, например unique: status [мое имя поля], он возвращает только первый результат, а не желаемый список всех доступных уникальных статуй. Есть догадки почему?
SinSync
3
Хотя я использовал подчеркивание, это решение отлично работало. Спасибо!
Джон Блэк
Очень элегантное решение.
JD Smith
1
lodash - это вилка подчеркивания. У него лучшая производительность и больше утилит.
Майк Уорд,
2
в lodash v4 _.uniqBy(arr, field);должен работать для вложенных свойств
ijavid
30

Вы можете использовать фильтр unique (aliases: uniq) в модуле angular.filter

использование: colection | uniq: 'property'
вы также можете фильтровать по вложенным свойствам: colection | uniq: 'property.nested_property'

Вы можете сделать что-то подобное ...

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: мы фильтруем по идентификатору клиента, т. Е. Удаляем повторяющихся клиентов.

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

результат
Список клиентов:
foo 10
bar 20
baz 30

a8m
источник
нравится ваш ответ, но мне трудно перевести его на мою проблему. Как бы вы справились с вложенным списком. Например, список заказов, содержащий список товаров для каждого заказа. В вашем примере у вас есть один клиент на заказ. Я хочу видеть уникальный список товаров по всем заказам. Не вдаваясь в подробности, но вы также можете думать об этом, как о том, что у клиента много заказов, а в заказе много позиций. Как я могу показать все предыдущие товары, которые заказал клиент? Мысли?
Грег Гратер
@GregGrater Можете ли вы предоставить пример объекта, похожего на вашу проблему?
a8m
Спасибо @Ariel M.! Вот пример данных:
Грег Гратер
С какими версиями angular он совместим?
Icet
15

этот код работает у меня.

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

а потом

var colors=$filter('unique')(items,"color");
Эдуардо Ортис
источник
2
Определение l должно быть l = arr! = Undefined? arr.length: 0, так как иначе в angularjs будет ошибка синтаксического анализа
Геррит
6

Если вы хотите перечислить категории, я думаю, вы должны явно указать свое намерение в представлении.

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

в контроллере:

$scope.categories = $scope.places.reduce(function(sum, place) {
  if (sum.indexOf( place.category ) < 0) sum.push( place.category );
  return sum;
}, []);
вздор
источник
не могли бы вы объяснить это немного подробнее. Я хочу повторить th элементы подряд для каждой уникальной категории. JS просто входит в JS-файл, в котором определен контроллер?
mark1234
Если вы хотите сгруппировать варианты, это другой вопрос. Вы можете изучить элемент optgroup. Angular поддерживает optgroup. поиск group byвыражения в selectдирективе.
Tosh
Что будет в случае добавления / удаления места? Будет ли добавлена ​​/ удалена связанная категория, если это единственный экземпляр в местах?
Грег Тертер,
4

Вот простой и общий пример.

Фильтр:

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

Разметка:

<div ng-repeat = "item in items | unique:'name'">
  {{ item.name }}
</div>
<script src="your/filters.js"></script>
Эрик Траутман
источник
Это нормально работает, но Cannot read property 'length' of undefinedв строке выдает ошибку в консолиl = arr.length
Soul Eeater
4

Я решил расширить ответ @ thethakuri, чтобы разрешить любую глубину для уникального члена. Вот код Это для тех, кто не хочет включать весь модуль AngularUI только для этой функции. Если вы уже используете AngularUI, проигнорируйте этот ответ:

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

пример

<div ng-repeat="item in items | unique : 'subitem.subitem.subitem.value'"></div>
kennasoft
источник
2

У меня был массив строк, а не объектов, и я использовал такой подход:

ng-repeat="name in names | unique"

с этим фильтром:

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
источник
2

ОБНОВИТЬ

Я рекомендовал использовать Set, но сожалею, что это не работает ни для ng-repeat, ни для Map, поскольку ng-repeat работает только с массивом. Так что проигнорируйте этот ответ. в любом случае, если вам нужно отфильтровать дубликаты одним способом, как сказал другой angular filters, вот ссылка на него в раздел «Начало работы» .


Старый ответ

Вы можете использовать стандартную структуру данных набора ECMAScript 2015 (ES6) вместо структуры данных массива, таким образом вы фильтруете повторяющиеся значения при добавлении в набор. (Помните, что наборы не допускают повторения значений). Действительно проста в использовании:

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
источник
2

Вот способ сделать это только с помощью шаблона (хотя и не поддерживает порядок). Плюс результат тоже будет заказан, что пригодится в большинстве случаев:

<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>
shoesel
источник
2

Ни один из вышеперечисленных фильтров не устранил мою проблему, поэтому мне пришлось скопировать фильтр из официального документа github. А затем используйте его, как описано в ответах выше.

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

});
Черная мамба
источник
1

Кажется, каждый бросает uniqueна ринг свою версию фильтра, так что я сделаю то же самое. Критика очень приветствуется.

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
источник
1

Если вы хотите получить уникальные данные на основе вложенного ключа:

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

Назовите это так:

<div ng-repeat="notify in notifications | unique: 'firstlevel':'secondlevel'">
thethakuri
источник
0

Добавьте этот фильтр:

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

Обновите разметку:

<select ng-model="orderProp" >
   <option ng-repeat="place in places | unique" value="{{place.category}}">{{place.category}}</option>
</select>
Шон Доти
источник
0

Это может быть излишним, но для меня это работает.

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

Отдельная функция зависит от функции contains, определенной выше. Его можно назвать array.distinct(prop);где prop - это свойство, которое вы хотите отличить.

Так что ты мог просто сказать $scope.places.distinct("category");

Икечи Майкл
источник
0

Создайте свой собственный массив.

<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);
    }
});
Ахилеш Кумар
источник