วิธีสร้าง 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 เพื่อตรวจสอบว่า a{{place.category}}อยู่แล้วและไม่สร้างตัวเลือกหากมีอยู่แล้ว

แก้ไข: ในจาวาสคริปต์ของฉัน$scope.places = JSON dataเพียงเพื่อชี้แจง


1
ทำไมคุณไม่ลบข้อมูล $ scope.places ของคุณใหม่ ใช้ jquery map api.jquery.com/map
anazimok

โซลูชันสุดท้ายของคุณคืออะไร มันอยู่เหนือหรือมี JS บางตัวกำลังทำการ de-duping
mark1234

ฉันต้องการทราบวิธีแก้ปัญหานี้ กรุณาโพสต์ติดตาม ขอบคุณ!
jdstein1

@ jdstein1 ฉันจะเริ่มต้นด้วย TLDR: ใช้คำตอบด้านล่างหรือใช้ vanilla Javascript เพื่อกรองเฉพาะค่าที่ไม่ซ้ำกันในอาร์เรย์ สิ่งที่ฉันทำ: ท้ายที่สุดมันเป็นปัญหากับตรรกะและความเข้าใจของฉันเกี่ยวกับ MVC ฉันกำลังโหลดข้อมูลจาก MongoDB เพื่อขอการถ่ายโอนข้อมูลและต้องการให้ Angular กรองข้อมูลให้เหลือเพียงสถานที่ที่ไม่ซ้ำกันอย่างน่าอัศจรรย์ วิธีแก้ปัญหาก็เหมือนกับว่ามักจะเกิดขึ้นคือเลิกขี้เกียจและแก้ไขโมเดล DB ของฉัน - สำหรับฉันแล้วมันเรียก Mongo db.collection.distinct("places")ซึ่งไกลดีกว่าการทำใน Angular! น่าเศร้าที่สิ่งนี้ใช้ไม่ได้กับทุกคน
JVG

ขอบคุณสำหรับการอัพเดท!
jdstein1

คำตอบ:


142

คุณสามารถใช้ตัวกรองเฉพาะจาก AngularUI (ซอร์สโค้ดมีอยู่ที่นี่: ตัวกรองเฉพาะ AngularUI ) และใช้โดยตรงในตัวเลือก ng (หรือ 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']);เช่นคุณต้องรวมไว้เป็นข้อมูลอ้างอิงเพิ่มเติมในโมดูลของคุณ นิ่งงันจนกระทั่งฉันเข้าไปดูในไฟล์ AngularUI js
GFoley83

8
ปัจจุบันuniqueตัวกรองสามารถพบได้เป็นส่วนหนึ่งของAngularJs UI Utils
Nick

2
ในยูทิลิตี้ ui เวอร์ชันใหม่คุณสามารถรวม ui.unique ได้ด้วยตัวเอง ใช้ bower ติดตั้งเฉพาะสำหรับโมดูล
สถิติการเรียนรู้ตามตัวอย่าง

หากคุณไม่ต้องการรวม AngularUI และความครบถ้วน แต่ต้องการใช้ตัวกรองเฉพาะคุณสามารถคัดลอกและวางซอร์ส js ที่ไม่ซ้ำกันลงในแอปของคุณจากนั้นเปลี่ยนangular.module('ui.filters')เป็นชื่อแอปของคุณ
chakeda

37

หรือคุณสามารถเขียนฟิลเตอร์ของคุณเองโดยใช้ lodash

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

สวัสดีไมค์ดูสง่างามมาก แต่ดูเหมือนจะไม่เป็นไปตามที่คาดไว้เมื่อฉันผ่านตัวกรองเช่น unique: status [ชื่อฟิลด์ของฉัน] มันจะส่งคืนผลลัพธ์แรกเท่านั้นเมื่อเทียบกับรายการที่ต้องการของรูปปั้นเฉพาะทั้งหมดที่มี เดาว่าทำไม?
SinSync

3
แม้ว่าฉันจะใช้ขีดล่าง แต่วิธีนี้ก็ใช้ได้ผลเหมือนมีเสน่ห์ ขอบคุณ!
Jon Black

โซลูชันที่หรูหรามาก
JD Smith

1
lodash คือส้อมของขีดล่าง มีประสิทธิภาพที่ดีขึ้นและมีระบบสาธารณูปโภคมากขึ้น
Mike Ward

2
ในlodash v4 _.uniqBy(arr, field);ควรใช้กับคุณสมบัติที่ซ้อนกัน
ijavid

30

คุณสามารถใช้ตัวกรอง 'unique' (นามแฝง: 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


เช่นเดียวกับคำตอบของคุณ แต่มีปัญหาในการแปลเป็นปัญหาของฉัน คุณจะจัดการกับรายการที่ซ้อนกันอย่างไร ตัวอย่างเช่นรายการคำสั่งซื้อที่มีรายการสินค้าสำหรับแต่ละคำสั่งซื้อ ในตัวอย่างของคุณคุณมีลูกค้าหนึ่งรายต่อคำสั่งซื้อ ฉันต้องการดูรายการสินค้าที่ไม่ซ้ำกันในทุกคำสั่งซื้อ ไม่ใช่เพื่อให้เข้าใจผิด แต่คุณสามารถคิดได้เช่นกันว่าลูกค้ามีคำสั่งซื้อจำนวนมากและคำสั่งซื้อมีหลายรายการ ฉันจะแสดงรายการก่อนหน้าทั้งหมดที่ลูกค้าสั่งซื้อได้อย่างไร? คิด?
Greg Grater

@GregGrater คุณสามารถให้ตัวอย่างวัตถุที่คล้ายกับปัญหาของคุณได้หรือไม่?
a8m

ขอบคุณ @Ariel M. ! นี่คือตัวอย่างของข้อมูล:
Greg Grater

มันเข้ากันได้กับ 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
Gerrit

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คำสั่ง

จะเกิดขึ้นในกรณีที่มีการเพิ่ม / ลบสถานที่หรือไม่? หมวดหมู่ที่เกี่ยวข้องจะถูกเพิ่ม / ลบออกหรือไม่หากเป็นเพียงอินสแตนซ์ในสถานที่
Greg Grater

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

UPDATE

ฉันกำลังแนะนำการใช้ 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 (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;
}

ฟังก์ชันที่แตกต่างกันขึ้นอยู่กับฟังก์ชันมีที่กำหนดไว้ข้างต้น ก็สามารถเรียกได้ว่า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);
    }
});
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.