Creating a simple predicate builder with AngularJS

By · Published · javascript, angularjs, howto

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!

( Comments )

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.


Related Posts

Extending ngResource To Access Metadata

Multiple Calibre Servers under Mac OS X


comments powered by Disqus