AngularJS

Search for '{{search_term}}'

AngularJS: filter table created with ng-repeat

CMOS is the Code-Maven Open Source podcast that also includes video interviews. Subscribe to this feed RSS feed with your Podcast listener app or via iTunes iTunes.

Creating a table using ng-repeat is quite simple. Adding a search box to filter the result is also quite simple, if we would like to use simple text search. It becomes a bit more complex if you'd like to search for values less than a certain value typed in by the user.

In our examples we are going to use the planets of the Solar System. We have the name, the average distance from the Sun measured in units of "distance of the Earth from the Sun", and the mass relative to the mass of the Earth. Therefore both numbers are 1 for the Earth. (We are not Earth centric at all, are we :).

In the first example we have built a table from some data embedded in the code and added a text-filter to all of the fields:

examples/angular/angular_table_filter_1.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport"
     content="width=device-width, initial-scale=1, user-scalable=yes">
  <title>Table Filter</title>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
  <script>
  angular.module('TableFilterApp', [])
    .controller('TableFilterController', function($scope) {
      $scope.planets = [
        {
          name : 'Mercury',
          distance : 0.4,
          mass : 0.055
        },
        {
          name : 'Venus',
          distance : 0.7,
          mass : 0.815
        },
        {
          name : 'Earth',
          distance: 1,
          mass : 1
        },
        {
          name : 'Mars',
          distance : 1.5,
          mass : 0.107
        },
        {
          name : 'Ceres',
          distance : 2.77,
          mass :     0.00015
        },
        {
          name : 'Jupiter',
          distance : 5.2,
          mass :   318
        },
        {
          name : 'Saturn',
          distance : 9.5,
          mass :    95
        },
        {
          name : 'Uranus',
          distance : 19.6,
          mass :   14
        },
        {
          name : 'Neptune',
          distance : 30,
          mass : 17
        },
        {
          name : 'Pluto',
          distance : 39,
          mass : 0.00218
        },
        {
          name : 'Charon',
          distance : 39,
          mass :  0.000254
        }
      ];
    
    });
  </script>

</head>
<body ng-app="TableFilterApp" ng-controller="TableFilterController">

<table>
<tr><th>Name</th><th>Average Distance</th><th>Mass</th></tr>
<tr><td><input ng-model="f.name"></td><td><input ng-model="f.distance"></td></td><td><input ng-model="f.mass"></td></tr>
<tr ng-repeat="p in planets | filter:f"><td>{{p.name}}</td><td>{{p.distance}}</td><td>{{p.mass}}</td></tr>
</table>
</body>
</html>


Try!

The interesting part of the code is in these two lines:

examples/angular/angular_table_filter_1.js

<tr><td><input ng-model="f.name"></td><td><input ng-model="f.distance"></td></td><td><input ng-model="f.mass"></td></tr>
<tr ng-repeat="p in planets | filter:f"><td>{{p.name}}</td><td>{{p.distance}}</td><td>{{p.mass}}</td></tr>

The second row is the one that builds the table. It is a regular ng-repeat row, but we have filtered out the results based on the content of object f. The attributes of that object are bound to the input boxes in the first line. Each attribute name will filter in the respective field of the original array.

That's neat, but in our case the search or filter only makes sense on the first column. As it is a simple text-filter there is no much use of finding all the planets for which the distance has a digit 7 in them. In other tables this might be more interesting.

Search values less than or greater than

A much more interesting search or filter would be to find all the planets where the distance is less than 2. Or where the mass is less than 20.

examples/angular/angular_table_filter_2.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport"
     content="width=device-width, initial-scale=1, user-scalable=yes">
  <title>Table Filter</title>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
  <script>
  angular.module('TableFilterApp', [])
    .controller('TableFilterController', function($scope) {
      $scope.planets = [
        {
          name : 'Mercury',
          distance : 0.4,
          mass : 0.055
        },
        {
          name : 'Venus',
          distance : 0.7,
          mass : 0.815
        },
        {
          name : 'Earth',
          distance: 1,
          mass : 1
        },
        {
          name : 'Mars',
          distance : 1.5,
          mass : 0.107
        },
        {
          name : 'Ceres',
          distance : 2.77,
          mass :     0.00015
        },
        {
          name : 'Jupiter',
          distance : 5.2,
          mass :   318
        },
        {
          name : 'Saturn',
          distance : 9.5,
          mass :    95
        },
        {
          name : 'Uranus',
          distance : 19.6,
          mass :   14
        },
        {
          name : 'Neptune',
          distance : 30,
          mass : 17
        },
        {
          name : 'Pluto',
          distance : 39,
          mass : 0.00218
        },
        {
          name : 'Charon',
          distance : 39,
          mass :  0.000254
        }
      ];
      $scope.f = {};

      $scope.filter_by = function(field) {
        console.log(field);
        console.log($scope.g[field]);
        if ($scope.g[field] === '') {
             delete $scope.f['__' + field];
             return;
        }
        $scope.f['__' + field] = true;
        $scope.planets.forEach(function(v) { v['__' + field] = v[field] < $scope.g[field]; })
      }
    
    });
  </script>

</head>
<body ng-app="TableFilterApp" ng-controller="TableFilterController">

<table>
<tr><th>Name</th><th>Average Distance</th><th>Mass</th></tr>
<tr><td><input ng-model="f.name"></td><td><input ng-model="g.distance" ng-change="filter_by('distance')"></td><td><input ng-model="g.mass" ng-change="filter_by('mass')"></td></tr>
<tr ng-repeat="p in planets | filter:f"><td>{{p.name}}</td><td>{{p.distance}}</td><td>{{p.mass}}</td></tr>
</table>
</body>
</html>


Try!

This is more complex. I could not find a better solution yet, so for now, every time the user types in a value in either of the "distance" or the "mass" filter box, the code will go over the values of the array, and add an extra key to each object which is true if the object matches the currently added filter, or false if it isn't. The extra key is build using two underscores '__' followed by the original key. The assumption is that it is very unlikely that we'll encounter data where these are also real keys.

The HTML part has changed slightly. Instead of binding the input boxes to attributes of the filter object, we bind them to another object (called g), and we also connect the input boxes to functions calls using ng-change.

examples/angular/angular_table_filter_2b.js

<tr><td><input ng-model="f.name"></td><td><input ng-model="g.distance" ng-change="filter_by('distance')"></td><td><input ng-model="g.mass" ng-change="filter_by('mass')"></td></tr>

When the user changes either of the two boxes, Angular will run the filter_by function. There first we check if the input box is empty. If it is, then we remove the whole condition.

If there is a value in the input box, we go over all the object in the planets array and add the attribute with the appropriate true or false value. The actual filtering is done by AngularJS.

examples/angular/angular_table_filter_2.js

      $scope.f = {};

      $scope.filter_by = function(field) {
        if ($scope.g[field] === '') {
             delete $scope.f['__' + field];
             return;
        }
        $scope.f['__' + field] = true;
        $scope.planets.forEach(function(v) { v['__' + field] = v[field] < $scope.g[field]; })
      }
 

Comments

In the comments, please wrap your code snippets within <pre> </pre> tags and use spaces for indentation.
comments powered by Disqus