Wednesday, October 18, 2017

Incremental Search in Angular

In an earlier post I showed how to implement an Incremental Search in WPF using MVVM. I recently decided I needed an Incremental Search in an Angular web page. It was trivial, but here it is anyway.

Start a new Web Application called IncrementalSearchAngular. You can use VB or C# and target any framework from 4.0 onward. Select the Empty template.


Add an HTML page called SearchPeople and make it the default page. Our first step is to create a simple page that lists some names. Make SearchPeople.html look like this.

<!DOCTYPE html>
<html ng-app="SearchPeopleApp">
<head>
    <title>Search People</title>
<meta charset="utf-8" />
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular-sanitize.js"></script>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <script>
        angular.module("SearchPeopleApp", ['ngSanitize'])
        .controller("SearchPeopleCtrl", function ($scope) {
            $scope.data = [{ First: 'Bob', Last: 'Smith' }, { First: 'Mary', Last: 'Jones' }, { First: 'Gary', Last: 'Flowers' }, { First: 'Anne', Last: 'Green' }];
        })
    </script>
</head>
<body ng-controller="SearchPeopleCtrl">
    <form name="MainForm">
        <div class="well">
            <table class="table">
                <thead>
                    <tr>
                        <th>First Name</th>
                        <th>Last Name</th>
                    </tr>
                </thead>
                <tbody>
                    <tr ng-repeat="item in data" ng-form="MainForm">
                        <td>{{item.First}}</td>
                        <td>{{item.Last}}</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </form>
</body>
</html>

You can run the application now and you will see the names listed.
Now let's add a search field to the UI. Immediately below the form tag, insert the following HTML.

<div class="well">
    <label>
        Search
        <input type="text" ng-model="Search"/>
    </label>
</div>

This will populate the $scope variable called Search with whatever the user enters into the textbox. Let's add that $scope variable now. Under the line that starts "$scope.data", add this line.

$scope.Search = "";

Now we need to write the filter that will only return names that contain the search term. Above the last <SCRIPT/> line, insert the following filter definition. This filter receives a list of names and the search term. Any names that contain the search term (case insensitive) are appended to a new list. When it is done, the filter returns the new list. Filters can be daisy chained together, so don't assume the list of names passed to the filter is the same list that was defined in $scope, or even has the same format.

.filter("applySearch",
function () {
    return function (data, Search) {
        var results = [];
        if (Search == "") return data;
        Search = Search.toUpperCase();
        for (var i = 0; i < data.length; i++) {
            if (data[i].First.toUpperCase().indexOf(Search) > -1)
                results.push(data[i]);
            else if (data[i].Last.toUpperCase().indexOf(Search) > -1)
                results.push(data[i]);
        }
        return results
    }
})

Your <HEAD> section should now look like this.

<head>
    <title>Search People</title>
    <meta charset="utf-8" />
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular-sanitize.js"></script>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <script>
        angular.module("SearchPeopleApp", ['ngSanitize'])
        .controller("SearchPeopleCtrl", function ($scope) {
            $scope.data = [{ First: 'Bob', Last: 'Smith' }, { First: 'Mary', Last: 'Jones' }, { First: 'Gary', Last: 'Flowers' }, { First: 'Anne', Last: 'Green' }];
            $scope.Search = "";
        })
            .filter("applySearch",
            function () {
                return function (data, Search) {
                    var results = [];
                    if (Search == "") return data;
                    Search = Search.toUpperCase();
                    for (var i = 0; i < data.length; i++) {
                        if (data[i].First.toUpperCase().indexOf(Search) > -1)
                            results.push(data[i]);
                        else if (data[i].Last.toUpperCase().indexOf(Search) > -1)
                            results.push(data[i]);
                    }
                    return results
                }
            })

    </script>
</head>

Now you have to use the new filter in the ng-repeat clause. Change the ng-repeat to look like this.

ng-repeat="item in data | applySearch:Search"

Angular is smart enough to know that whenever $scope.Search changes, the applySearch filter needs to be reevaluated. We don't have to explicitly wire up any event handlers.

Implementing an incremental search in Angular has only four simple steps.
1. Create the backing store ($scope.Search)
2. Bind a textbox to the backing store
3. Write the filter (applySearch)
4. Alter the ng-repeat to apply the new filter.




No comments:

Post a Comment