0

I need to filter an array by using nested objects, it means filter the array by using multiple check boxes but I have an issue with the duplicate names. This is the source code I've been trying to use.

Any idea about how to fix the duplicate names issue?

html

<div data-ng-app="myApp">
<div data-ng-controller="controller">

<strong>Pick a brand to see the models</strong>
<div ng-init="group = (cars | groupBy:'make')">
    <div ng-repeat="m in group">
        <b><input type="checkbox" checked="true" ng-model="useMakes[$index]"/>{{m.name}}</b>
    </div>
</div>

<br/>
<table border="1">
    <thead>
       <tr>
           <th>#</th>
           <th>Name</th>
    <th>Maker</th>
  </tr>
</thead>
<tbody>
  <tr ng-repeat="car in cars | filter:filterMakes()">
    <td>{{$index+1}}</td>
    <td>{{car.make.name}}</td>
    <td>{{car.model}}</td>
  </tr>
</tbody>

js

var myApp = angular.module('myApp',[]);

myApp.controller("controller", function($scope){
    $scope.useMakes = [];

    $scope.filterMakes = function () {
        return function (p) {
        for (var i in $scope.useMakes) {
             if (p.make == $scope.group[i] && $scope.useMakes[i]) {
             return true;
             }
        }
    };
 };

 $scope.makes = [
     {id:1, name: "BMV"},
     {id:2, name: "Ford"},
     {id:3, name: "Renault"},
     {id:4, name: "Seat"},
     {id:5, name: "Opel"}
 ];

 $scope.cars = [
     {model: '316', make: {id: 1, name: "BMV"}},
     {model: '520', make: {id: 1, name: "BMV"}},
     {model: 'Fiesta', make: {id: 2, name: "Ford"}},
     {model: 'Focus', make: {id: 2, name: "Ford"}},
     {model: 'Clio', make: {id: 3, name: "Renault"}},
     {model: 'Toledo', make: {id: 4, name: "Seat"}},
     {model: 'Leon', make: {id: 4, name: "Seat"}},
     {model: 'Insignia', make: {id: 5, name: "Opel"}},
     {model: 'Astra', make: {id: 5, name: "Opel"}},
     {model: 'Corsa', make: {id: 5, name: "Opel"}}
 ];

 });

 /*I think here is the problem*/
 var uniqueItems = function (data, key) {
     var result = new Array();
     for (var i = 0; i < data.length; i++) {
     var value = data[i][key];

    if (result.indexOf(value) == -1) {
        result.push(value);
    }

 }
 return result;
 };

 myApp.filter('groupBy', function () {
     return function (collection, key) {
     if (collection === null) return;
     return uniqueItems(collection, key);
     };
});
3
  • @Sajeetharan Hi, can you give me a hand with this little issue? Thanks. Commented Oct 27, 2016 at 0:10
  • Can you make a JSfiddle or a Codepen, so we can play with the code to help you out more? Commented Oct 27, 2016 at 0:47
  • @BrianLogan Sure, here is the code jsfiddle.net/wefs/rqwoxvzt Commented Oct 27, 2016 at 11:36

3 Answers 3

1

You can use the 'unique' filter

   <div ng-repeat="m in group | unique:'name'">
            <b><input type="checkbox" checked="true" ng-model="useMakes[$index]"/>{{m.name}}</b>
   </div>

DEMO

Sign up to request clarification or add additional context in comments.

Comments

1

You could use the unique filter from AngularUI (source code available here: AngularUI unique filter) and use it directly in the ng-options (or 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>

Comments

0

The root of problem is that when using indexOf in the uniqueItems(), the object reference (a.k.a memory address) is being used for comparison to check whether something is already in the array.

The effor to uniqify won't work as expected because the $scope.cars[0].make and $scope.cars[1].make are declared at 2 different places and hence will be created as 2 diffferent object references.

There are two possible solutions:

Alternative Solution #1 (preferred): make every make in the $scope.cars to refer to objects that are already declared in the $scope.makes. This is the preferred solution because you will have one central place that specifies what the make names are (Single Source Of Truth), i.e. you only need to change one place if later you want to fix the "BMV" to "BMW".

$scope.cars = [
  {model: '316', make: $scope.makes[0]},
  {model: '520', make: $scope.makes[0]},
  {model: 'Fiesta', make: $scope.makes[1]},
  {model: 'Focus', make: $scope.makes[1]},
  {model: 'Clio', make: $scope.makes[2]},
  {model: 'Toledo', make: $scope.makes[2]},
  {model: 'Leon', make: $scope.makes[3]},
  {model: 'Insignia', make: $scope.makes[4]},
  {model: 'Astra', make: $scope.makes[4]},
  {model: 'Corsa', make: $scope.makes[4]}
];

Alternative Solution #2: if you want to keep the $scope.cars for some reasons, we need to fix the $scope.filterMakes() and uniqueItems() to only compare the unique attribute of make instead of the make object reference. In this case, we can compare the make.id.

$scope.filterMakes = function () {
    return function (p) {
        for (var i in $scope.useMakes) {
            if (p.make.id == $scope.group[i].id && $scope.useMakes[i]) {  //add the ".id"
                return true;
            }
        }
    }
};

var uniqueItems = function (data, key) {
    var result = new Array();
    var added = []; //same as new Array();
    for (var i = 0; i < data.length; i++) {
        var value = data[i][key];
        if (added.indexOf(value.id) == -1) { //look for the id
            result.push(value);
            added.push(value.id); //list of added ids
        }
    }

    return result;
};

6 Comments

Thanks, I'll choose the #2 solution.
Hi, I have a little issue based in your answer, please see the readme file from this example plnkr.co/edit/W9OKntBW7sIsm5xDUbOJ?p=preview in order to get a more detailed explanation. I'll really appreciate your help. Thanks.
Basically, the json loading is an async process, so we need to run your ng-init code right after $scope.cars has been loaded because $scope.group depends on it. Additionally, in the groupBy we need to handle when collection is undefined. I have slightly modified your code, see plnkr.co/edit/VTdfVw1sNKUU2jpYBQQ9
Sorry man, it's me again. I have been trying to understand how to set all checkboxes selected by default. Any idea? Thanks.
initialize your $scope.useMakes. Add this for (var i=0; i< $scope.group.length; i++) $scope.useMakes[i] = true; just right after you initialize the $scope.group. I have updated my plunkr above.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.