Filter on deep object properties in angularjs

AngularJS provides a neat way of filtering arrays in an ng-repeat tag by piping elements to a built in filter filter which will filter based on a predicate. Doing it this way you can filter items based on a function, or an expression (evaluated to a literal), or by an object.

When filtering by an object you can pass in a javascript object that represents key value items that should match whats in the backing data element. For example:

$scope.elements = [{ foo: "bar" }, { foo: "biz" }]

--

<div ng-repeat="foos in elements | filter: { foo: "bar" }">
  {{ foos.foo }} matches "bar"
</div>

Here I filtered all the objects whose foo property matches the value “bar”. But what if I have a non-trivial object? Something with lots of nested objects? I found that passing in the object to the filter was both unweidly, and error prone. I wanted something simple where I could write out how to dereference it, like obj.property.otherItem.target.

Thankfully this is pretty easy to write:

function initFilters(app){
    app.filter('property', property);
}

function property(){
    function parseString(input){
        return input.split(".");
    }

    function getValue(element, propertyArray){
        var value = element;

        _.forEach(propertyArray, function(property){
            value = value[property];
        });

        return value;
    }

    return function (array, propertyString, target){
        var properties = parseString(propertyString);

        return _.filter(array, function(item){
            return getValue(item, properties) == target;
        });
    }
}

And can be used in your html like this:

<ul>
    only failed: <input type="checkbox"
                      ng-model="onlyFailed"
                      ng-init="onlyFailed=false"/>

    <li ng-repeat="entry in data.entries | property:'test.status.pass':!onlyFailed">
        <test-entry test="entry.test"></test-entry>
    </li>
</ul>

6 comments

  1. Dave Sanderling

    Hey, this is awesome. Saved me some time, thank you! I was wondering why angular wasn’t filtering on deep properties.

  2. Gio Zunino

    Great work Anton!
    This issue costed me a couple of hours after upgrading Angular from 1.2.9 to 1.2.12

    It shouldn’t be necessary anymore since the latest Angular release (1.2.13) from last friday (February 14)
    Angular Changelog

    To make it work, have your filter-comparator-object mirror the structure of the iterator object and bind the property you want to filter to some form element, for example:


    <input type="text" ng-model="item.with.a.deep.object.property">

  3. Gio Zunino

    Forgot the 2nd part :)

    <ul>
    <li ng-repeat="myItem in items | filter: item">
    <!-- Do some angular magic with deep nested properties -->
    </li>
    </ul>

  4. CruX

    For people wanting a version of this that doesn’t require underscore.js.


    var module = angular.module('app', []);

    module.filter("property", ["$filter", function($filter){
    var parseString = function(input){
    return input.split(".");
    }

    function getValue(element, propertyArray) {
    var value = element;

    angular.forEach(propertyArray, function(property) {
    value = value[property];
    });

    return value;
    }

    return function (array, propertyString, target) {
    var properties = parseString(propertyString);

    return $filter('filter')(array, function(item){
    return getValue(item, properties) == target;
    });
    }
    }]);

Post a comment

You may use the following HTML:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>