mhatch
mhatch

Reputation: 4605

ng-show not re-evaluating after filter results change

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

Answers (1)

S&#233;bastien
S&#233;bastien

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

Related Questions