Creating a simple predicate builder with AngularJS

So I’ve been working on a project recently where I needed a simple predicate builder. Basically I needed a way to allow users to build a somewhat complex search using a GUI. And since we are using AngularJS on this project, here’s a quick article about how I did it.

Start with a simple AngularJS setup.

angular
    .module('predicate', []);

angular
    .module('predicate')
    .factory('Condition', function() {
        function Condition(data) {
            if (data) {
                this.setData(data);
            }
        }

        Condition.prototype.setData = function(data) {
            angular.extend(this, data);
        };

        Condition.prototype.searchKey = "";
        Condition.prototype.searchValue = "";

        return Condition;
    });

angular
    .module('predicate')
    .controller('predicate', function ($scope, Condition) {
        var defaultSearch = {"searchKey": "id", "searchValue": ""};
        $scope.conditions = [new Condition(defaultSearch)];
    });

Okay, so what we’ve done so far is stub out a simple AngularJS app, with a factory that builds a Condition object. This will be the model object that we use to hold data from our predicates. We also add a starting predicate so that there’s at least one condition to search by when the user opens the page.

Now, let’s add some HTML:

<body ng-app="predicate">
    <div ng-controller="predicate">
        <form ng-submit="doSearch()">
            <div ng-repeat="condition in conditions">
                <select ng-model="condition.searchKey">
                    <option value="id">ID</option>
                    <option value="name">Name</option>
                    <option value="email">Email</option>
                </select>
                <input type="text" ng-model="condition.searchValue">
                <button type="button" ng-show="$last" ng-click="addCondition()">+</button>
                <button type="button" ng-show="conditions.length > 1" ng-click="deleteCondition(condition)">-</button>
            </div>
            <button type="button" ng-click="doSearch()">Search</button>
        </form>
    </div>
</body>

So we’ve created a simple HTML stub for an AngularJS app. A couple of notes about what we’re doing here. We only show the delete button if we have more than one condition because we don’t want to get into a situation where we have no conditions. We also only show the add button on the last condition, using the magic variable $last, which is available inside the ng-repeat loop.

So if you fire this up, you should see a simple predicate already in place, because you already have one Condition object in the conditions array.

So now we need to make it do something.

angular
    .module('predicate')
    .controller('predicate', function ($scope, Condition) {
        var defaultSearch = {"searchKey": "id", "searchValue": ""};
        $scope.conditions = [new Condition(defaultSearch)];

        $scope.addCondition = function() {
            $scope.conditions.push(new Condition(defaultSearch));
        }

        $scope.deleteCondition = function(condition) {
            $scope.conditions.splice($scope.conditions.indexOf(condition), 1);
        }
    });

Now, you should be able to add and remove conditions. Pretty neat! But how do we digest the objects we’re creating? Well, let’s do that now.

angular
    .module('predicate')
    .controller('predicate', function ($scope, Condition) {
        var defaultSearch = {"searchKey": "id", "searchValue": ""};
        $scope.conditions = [new Condition(defaultSearch)];

        $scope.addCondition = function() {
            $scope.conditions.push(new Condition(defaultSearch));
        }

        $scope.deleteCondition = function(condition) {
            $scope.conditions.splice($scope.conditions.indexOf(condition), 1);
        }

        $scope.doSearch = function() {
            var searches = {};
            $scope.conditions.forEach(function(val) {
                if (val.searchValue.length) {
                    searches[val.searchKey] = val.searchValue;
                }
            });

            if (Object.keys(searches).length == 0) {
                return;
            }

            console.log(searches);
        }
    });

So here, the new doSearch() method we added to this scope loops through the objects and builds a searches object. You can now use this object to query whatever backend resource you would like. I feed it into $httpParamSerializer to turn it into a query string for querying an API resource. Right now, we’re just printing that to the log using console.log() so you can see what it’s doing.

So there you have it! Here’s a CodePen that demonstrates the whole thing. Enjoy!

Did something I wrote help you out?

That's great! I don't earn any money from this site - I run no ads, sell no products and participate in no affiliate programs. I do this solely because it's fun; I enjoy writing and sharing what I learn.

All the same, if you found this article helpful and want to show your appreciation, here's my Amazon.com wishlist.

Read More

Extending ngResource To Access Metadata

AngularJS’s built-in ngResource is a great tool for natively supporting REST APIs in your Angular application. But what happens when you need to support something besides a simple call that retrieves a list of JSON objects? You quickly run into the limits of ngResource. Here’s a great case where you might need to do something more complex: paging. Say you want to get a list of objects, and there’s 10,000 or so of them. You don’t want to send 10,000 objects to your frontend app. You want to send a portion of them, but you still need to indicate to the app that there are more. Surprisingly, considering how widespread this pattern is in web development, there does not seem to be a native way to accomplish this. But you can extend ngResource. Here’s how I did it.

Rob's Raspberry Pi Powered Pet Feeders

Or, how to massively over-engineer dumping cat food into a bowl. As with many of my projects, it started with something that made me angry. In this case, it was this: The Petmate Le Bistro Pet Feeder. Okay, let’s back up a little bit. Back to about 8 or so years ago. We had a cat at the time, Pumpkin, who as objectively not a good cat. She was foul tempered on the best of days and very difficult to love. But she was my wife and I’s first pet, so we did love her all the same. She had a habit of wanting food precisely on time. And if it was late, she would raise all manner of noise until she was fed. Often this came at some ungodly early time in the morning. So I bought a Petmate Le Bistro Pet Feeder.