Как заставить 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чтобы уточнить


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>

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


нравится ваш ответ, но мне трудно перевести его на мою проблему. Как бы вы справились с вложенным списком. Например, список заказов, содержащий список товаров для каждого заказа. В вашем примере у вас есть один клиент на заказ. Я хочу видеть уникальный список товаров по всем заказам. Не вдаваясь в подробности, но вы также можете думать об этом, как о том, что у клиента много заказов, а в заказе много позиций. Как я могу показать все предыдущие товары, которые заказал клиент? Мысли?
Грег Гратер

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

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

  };
}

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

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>

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

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

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);
    }
});
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.