Angular - Filtering


In this video we are going to finish up the Angular Pagination, Filtering, and Sorting section by adding in a simple Filtering facility.

The aim of this filter is to restrict down the results to only those that match the given criteria. For the purposes of this video we will only implement this functionality for the title information.

One thing to note, as this relies on the API implementation for Filtering, there is a strong likelyhood that this will be too basic for a production setup. Please review the previous video and add your own Filtering implementation accordingly.

Much like in the previous three videos, we will rely on modifying the URL being used to call the API to implement our Filtering functioanlity.

We've already covered the fundamentals of how to do most of this whilst adding in the limit functionality in the previous video. Let's build on that knowledge now to add in the filter.

Adding a Filter input

The first thing we likely want to do is to add in an input box to allow a user to enter the text they would like to filter on.

The idea here is pretty simple - whatever text the user enters here will be matched against existing blog post title content. The more specific the text, likely the fewer results that will be returned.

Again, all this filtering logic happens on the back end. All Angular will do is construct the query, and display the results. And unlike in the Twig implementation, we don't need the page refresh.

<!-- /app/blogPost/list/list.html -->

<table class="table table-hover table-responsive">
    <thead>
    <tr>
        <th><span ng-click="sortBy('bp.id')">id</span></th>
        <th>
            <span ng-click="sortBy('bp.title')">Title</span>
            <input ng-model="filter" placeholder="filter">
        </th>

At this stage we should have a simple input element rendered next to the 'Title' text in the table heading.

We have told Angular that whatever input is entered here should be tied to the $scope.filter variable. This doesn't yet exist on the scope, so let's create that now:

// /app/blogPost/list/listController.js

'use strict';

angular.module('myApp.blogPost')

.controller('listController', ['$scope', 'Api', '$filter', function($scope, Api, $filter) {

    // * snip *

    $scope.filter = '';

And we leave this as an empty string to begin with, as we initially do not want any filtering.

Passing The Filter Parameter To The API

The next thing we need to do is to update the API calls to ensure the filter parameter is passed through. This may break some of our existing function calls, so we will need to fix those in a moment.

There's a few places we need to make this update, most notably:

// /app/blogPost/list/listController.js

'use strict';

angular.module('myApp.blogPost')

.controller('listController', ['$scope', 'Api', '$filter', function($scope, Api, $filter) {

    // * snip *

    var getBlogPosts = function (page, itemsPerPage, filter, sortBy, direction) {
        Api.getAll(page, itemsPerPage, filter, sortBy, direction)
            .then(function (result) {

                console.log('result', result);
                $scope.blogPosts = result.data.items;
                $scope.totalItems = result.data.totalCount;
                $scope.currentPage = result.data.currentPageNumber;

            }, function (error) {
                console.log('error', error);
            });
    };

and also in the Api.js file:

// /app/blogPost/list/listController.js

angular.module('myApp.blogPost')

    .factory('Api', [
        '$http',
        function ($http) {

            var ROOT_URL = 'http://api.symfony-3.dev/app_dev.php/posts';

            function getAll(page, offset, filter, sort, direction) {
                return $http({
                    url: ROOT_URL,
                    method: 'GET',
                    params: {
                        page: page || 1,
                        limit: offset || 10,
                        filter: filter || '',
                        sort: sort || '',
                        direction: direction || ''
                    }
                });
            }

Again, much like in previous videos we simply pass through the filter value, or default - in this case - to an empty string.

Watching For Changes

Still, this won't actually do anything.

We have a nice input box, and a way to pass through a filter parameter to our API, but we haven't hooked these two things together yet. Let's do that now.

We will do this using a $watch function, much like in the previous video.

Whenever a user changes the value in the filter input, Angular will detect the changed value and use this as the filter parameter needed to invokve the getBlogPosts function:

// /app/blogPost/list/listController.js

'use strict';

angular.module('myApp.blogPost')

.controller('listController', ['$scope', 'Api', '$filter', function($scope, Api, $filter) {

    // * snip *

    $scope.$watch('filter', function(newValue, oldValue) {
        console.log('filter changes', newValue, oldValue); // not used, just for demonstration
        getBlogPosts(1, $scope.itemsPerPage, $scope.filter, $scope.propertyName, reversedAsString($scope.reverse));
    });

And with that we have implemented our simple filtering logic.

It may be nicer to wrap the call to getBlogPosts inside a timeout, or use something like lodash to throttle calls to the API. The reasoning for this is that currently the API is queried on every single keystroke. This generates a whole bunch of noise - along with thrashing your API. This is the kind of problem that gets worse as your system grows in users.

Anyway, that wraps up the Angular portion of this series. In the next video we will start our React implementation.

Code For This Video

Get the code for this video.

Episodes