Reputation: 4605
I have a loop in AngularJS that filters array results using a text input. The filter works great; the list is updated immediately on text input. There is a bug, however, where the alpha headings get duplicated if you start typing a text value in the input, then (use backspace to) delete your entry.
For example, in the provided MCVE type "an" then delete the 2 letters to leave an empty text input (and render the complete list). You will see duplication of the alpha headings: "A", "B", "C", etc.
Why is the ng-show
used to display the headings, not re-evaluated when the ng-repeat
filter results change? How do I fix it?
var app = angular.module('countriesApp',[]);
app.controller('countriesCtrl', ['$scope', '$http', function($scope, $http) {
$scope.countriesJSON = [
['Afghanistan','Zone3','A'],['Albania','Zone3','A'],['Algeria','Zone2','A'],['Angola','Zone2','A'],['Antigua','Zone2','A'],['Argentina','Zone2','A'],['Armenia','Zone3','A'],['Azerbaijan','Zone2','A'],['Bangladesh','Zone3','B'],['Belarus','Zone2','B'],['Belize','Zone3','B'],['Benin','Zone3','B'],['Bhutan','Zone3','B'],['Bolivia','Zone3','B'],['Bosnia','Zone2','B'],['Botswana','Zone2','B'],['Brazil','Zone2','B'],['Bulgaria','Zone2','B'],['Burkina Faso','Zone3','B'],['Burundi','Zone3','B'],['C African Rp','Zone3','C'],['Cambodia','Zone3','C'],['Cameroon','Zone3','C'],['Cape Verde','Zone3','C'],['Chad','Zone3','C'],['Chile','Zone2','C'],['China','Zone3','C'],['Colombia','Zone2','C'],['Comoros','Zone3','C'],['Congo','Zone3','C'],['Cook Islands','Zone2','C'],['Costa Rica','Zone2','C'],['Cote DIvoire','Zone3','C'],['Cuba','Zone2','C'],['Djibouti','Zone3','D'],['Dominica','Zone2','D'],['Dominican Republic','Zone2','D'],['Ecuador','Zone2','E'],['Egypt','Zone3','E'],['El Salvador','Zone3','E'],['Eritrea','Zone3','E'],['Ethiopia','Zone3','E'],['Federal State of Micronesia','Zone3','F'],['Fiji','Zone3','F'],['French Guiana','Zone2','F']
];
$scope.alphaHeader = function(curAlpha) {
showHeader = (curAlpha != $scope.alpha);
$scope.alpha = curAlpha;
return showHeader;
}
}]);
/* --------------------------------------------------------------- *
* search countries *
* ---------------------------------------------------------------- */
app.filter('search_countries', function() {
return function(input, query) {
// return all countries if nothing in query box
if (!query) return input;
//split query terms by space character
var terms = query.split(' ');
var output = [];
// iterate through input array
input.forEach(function(country){
var found = false;
passTest = true;
// iterate through terms found in query box
terms.forEach(function(term){
// if all terms are found set boolean to true
found = (country[0].toLowerCase().indexOf(term.toLowerCase()) > -1);
passTest = passTest && found;
});
// Add country to output array only if passTest is true -- all search terms were found in product
if (passTest) { output.push(country); }
});
return output;
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="countriesApp" id="countriesApp" style="padding-bottom: 30px;">
<div ng-controller="countriesCtrl">
<!-- Search Countries -->
<div class="form-group row">
<label for="search" class="col-sm-12 col-md-5 col-lg-4 control-label">Select the country you reside in.</label>
<div class="col-sm-8 col-md-7 col-lg-5">
<input id="search" type="text" ng-model="search_input" placeholder="Search Countries" class="form-control" autocomplete='off' />
</div>
</div>
<div class="col-xs-12 col-sm-12"><hr></div>
<!-- Countries list -->
<div class="row">
<div class="" ng-repeat="country in results = (countriesJSON | search_countries: search_input)">
<div ng-show="alphaHeader(country[2])" class="col-xs-12"><h3>{{ country[2] }}</h3></div>
<div class="col-xs-6 col-sm-6 col-md-4 col-lg-3">
<a ng-href="country.html?zone={{ country[1] }}&country={{ country[0] }}">
{{ country[0] }}
</a>
</div>
</div>
</div>
</div>
</div>
Upvotes: 0
Views: 36
Reputation: 12139
Simply add track by $index
to your ng-repeat
.
<div class="" ng-repeat="country in results = (countriesJSON | search_countries: search_input) track by $index">
var app = angular.module('countriesApp',[]);
app.controller('countriesCtrl', ['$scope', '$http', function($scope, $http) {
$scope.countriesJSON = [
['Afghanistan','Zone3','A'],['Albania','Zone3','A'],['Algeria','Zone2','A'],['Angola','Zone2','A'],['Antigua','Zone2','A'],['Argentina','Zone2','A'],['Armenia','Zone3','A'],['Azerbaijan','Zone2','A'],['Bangladesh','Zone3','B'],['Belarus','Zone2','B'],['Belize','Zone3','B'],['Benin','Zone3','B'],['Bhutan','Zone3','B'],['Bolivia','Zone3','B'],['Bosnia','Zone2','B'],['Botswana','Zone2','B'],['Brazil','Zone2','B'],['Bulgaria','Zone2','B'],['Burkina Faso','Zone3','B'],['Burundi','Zone3','B'],['C African Rp','Zone3','C'],['Cambodia','Zone3','C'],['Cameroon','Zone3','C'],['Cape Verde','Zone3','C'],['Chad','Zone3','C'],['Chile','Zone2','C'],['China','Zone3','C'],['Colombia','Zone2','C'],['Comoros','Zone3','C'],['Congo','Zone3','C'],['Cook Islands','Zone2','C'],['Costa Rica','Zone2','C'],['Cote DIvoire','Zone3','C'],['Cuba','Zone2','C'],['Djibouti','Zone3','D'],['Dominica','Zone2','D'],['Dominican Republic','Zone2','D'],['Ecuador','Zone2','E'],['Egypt','Zone3','E'],['El Salvador','Zone3','E'],['Eritrea','Zone3','E'],['Ethiopia','Zone3','E'],['Federal State of Micronesia','Zone3','F'],['Fiji','Zone3','F'],['French Guiana','Zone2','F']
];
$scope.alphaHeader = function(curAlpha) {
showHeader = (curAlpha != $scope.alpha);
$scope.alpha = curAlpha;
return showHeader;
}
}]);
/* --------------------------------------------------------------- *
* search countries *
* ---------------------------------------------------------------- */
app.filter('search_countries', function() {
return function(input, query) {
// return all countries if nothing in query box
if (!query) return input;
//split query terms by space character
var terms = query.split(' ');
var output = [];
// iterate through input array
input.forEach(function(country){
var found = false;
passTest = true;
// iterate through terms found in query box
terms.forEach(function(term){
// if all terms are found set boolean to true
found = (country[0].toLowerCase().indexOf(term.toLowerCase()) > -1);
passTest = passTest && found;
});
// Add country to output array only if passTest is true -- all search terms were found in product
if (passTest) { output.push(country); }
});
return output;
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="countriesApp" id="countriesApp" style="padding-bottom: 30px;">
<div ng-controller="countriesCtrl">
<!-- Search Countries -->
<div class="form-group row">
<label for="search" class="col-sm-12 col-md-5 col-lg-4 control-label">Select the country you reside in.</label>
<div class="col-sm-8 col-md-7 col-lg-5">
<input id="search" type="text" ng-model="search_input" placeholder="Search Countries" class="form-control" autocomplete='off' />
</div>
</div>
<div class="col-xs-12 col-sm-12"><hr></div>
<!-- Countries list -->
<div class="row">
<div class="" ng-repeat="country in results = (countriesJSON | search_countries: search_input) track by $index">
<div ng-show="alphaHeader(country[2])" class="col-xs-12"><h3>{{ country[2] }}</h3></div>
<div class="col-xs-6 col-sm-6 col-md-4 col-lg-3">
<a ng-href="country.html?zone={{ country[1] }}&country={{ country[0] }}">
{{ country[0] }}
</a>
</div>
</div>
</div>
</div>
</div>
I actually just had a hunch as soon as I saw the word "duplication" in the context of an ng-repeat
.
I suppose because zones (in the countries array) can be the same for different countries it isn't safe to track by content, which is the default in AngularJS ng-repeat
.
Upvotes: 1