wilson wilson - 1 month ago 5
AngularJS Question

AngularJS - Filter array by using nested objects in check boxes

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

Answer

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