Filtering in Angular

Let's move along. Say we've added a bunch of students to our class and we need to search or filter the list of students. Angular provides a really simple way to accomplish this. Here's the new code:

student-roster/index.html

<body>
  <div class="container" ng-controller="StudentsCtrl">
    <div class="row">
      <div class="col-md-12">
        <h4>Search Students</h4>
        <form class="form-inline" role="form">
          <div class="form-group">
            <input ng-model="query" type="text" class="form-control" placeholder="Search">
          </div>
        </form>
      </div>
    </div>
    <div class="row">
      <div class="col-md-12">
        <h2>Student List</h2>
      </div>
      <div class="col-md-12">
        <ul>
          <li ng-repeat="item in students | filter:query">
            <span ng-click="editing = true" ng-hide="editing">
              {{item.name}} <a ng-click="deleteStudent(item)">Delete</a>
            </span>
            <span class="form-group" ng-show="editing" ng-submit="editing = false">
              <form class="form-inline" role="form">
                <input type="text" class="form-control input-sm" ng-model="item.name" placeholder="Name" required/>
                <button class="btn btn-default btn-sm" type="submit">Save</button>
              </form>
            </span>
          </li>
        </ul>
      </div>
    </div>
...
  </div>
</body>

Now if we start typing in the search field, it will automatically filter the results!

Let’s take a look at how this was done. First, we are adding a new form at the top of the page with a text field where we can enter a student's name to search for. In the input tag of our new form we use the ng-model directive to set a new property called query. Thanks to the two-way binding, this model will constantly remain updated as the user is typing.

We then use the filter filter (filter is only one type of filter in Angular) and pass it query as the expression. The filter will generate a new array of students based on the expression passed. Again, thanks to the two-way data binding, filter will automatically narrow the results displayed based on the changing value of our query model.

To make this page look a little cleaner, let's hide the search <div> and the Student List if no students have been added yet. Here’s the code:

student-roster/index.html

<body>
  <div class="container" ng-controller="StudentsCtrl">
    <div class="row" ng-show="students.length">
      <div class="col-md-12">
        <h4>Search Students</h4>
        <form class="form-inline" role="form">
          <div class="form-group">
            <input ng-model="query" type="text" class="form-control" id="student-name" placeholder="Search">
          </div>
        </form>
      </div>
    </div>
    <div class="row">
      <div class="col-md-12">
        <h2 ng-show="students.length && filtered.length">Student List</h2>
        <h2 ng-show="students.length && !filtered.length">No Matches</h2>
        <ul>
          <li ng-repeat="item in filtered = (students | filter:query)">
            <span ng-click="editing = true" ng-hide="editing">
              {{item.name}} <a ng-click="deleteStudent(item)">Delete</a>
            </span>
            <span class="form-group" ng-show="editing" ng-submit="editing = false">
              <form class="form-inline" role="form">
                <input type="text" class="form-control input-sm" ng-model="item.name" placeholder="Name" required/>
                <button class="btn btn-default btn-sm" type="submit">Save</button>
              </form>
            </span>
          </li>
        </ul>
      </div>
      <div class="col-md-4">
        <h2>New Student</h2>
        <form ng-submit="addStudent()" class="form-inline" role="form">
          <div class="form-group">
            <input type="text" ng-model="studentName" class="form-control" placeholder="Enter name">
          </div>
          <button type="submit" class="btn btn-default">Submit</button>
        </form>
      </div>
    </div>
  </div>
</body>

We've added ng-show="students.length" to the search <div>. If there are no students, then students.length will evaluate to 0, and 0 is falsy in JavaScript, so this will make the ng-show directive hide the search form.

Next, we only want to show the <h2> "Student List" if there are currently students in the roster, and students matching the search criteria. We do this with ng-show by telling it "Only show this element if both students.length and filtered.length are true".

We also create a second <h2> tag to display the words "No Matches" when there are students in the roster, but none of them match the student we are searching for. To do this we use ng-show again and tell it "only show this element when students.length is true, and when filtered.length is false."

Lastly, let's look at this line: ng-repeat="item in filtered = (students | filter:query)". It used to say ng-repeat="item in students", which allowed us to view all students. Here we are making a new variable called filtered to iterate through. Then we set it to students | filter:query. Breaking this down, filter:query refers to the filtered array of students that we made in our query model, and students still refers to all the students in the roster. The | symbol basically means or. This way, if the query returns anything (we found some student matches) then show the search results. If there has not been a query made, then display all students instead.

Check out other ways to filter data in the AngularJS API docs.

Alright, that's it for the basics of Angular!