AngularJS - Filter by any of the properties

1.9k Views Asked by At

I've got an application where I show a list of items with AngularJS. I'm trying to make an easy search on this list but it searches on everything inside that item. An example of an item:

{
    id: 283727893,
    name: 'Item A',
    parent: {
        id: 239495838,
        name: 'Item B'
    },
    user: 'User C'
}

In the list I'm only writing the name of the root item, so parent.name does not appear.

The problem

However, if I search using an AngularJS filter by 'Item B', it still appears, because an inner property has this string.

What I've tried

I've put an object reference such as the following (where val is the text of the input):

vm.searchObj = {
    name: val,
    user: val
};

And then, on my DOM:

<li data-ng-repeat="[...] | filter: Ctrl.searchObj | orderBy: Ctrl.orderBy">
...
</li>

However, this works with conditional AND, so it only shows if both, name and user have val.

What I need

I'd like to take an input text and make a search to filter this list on given object properties (in plural).

So, in this example, this item should appear if I write 'Item A' or 'User C' (or just 'A' or just 'C' and so on), but not if I write 'B', because none of name and user have this letter.

I'd need it to take the reference object, in this case Ctrl.searchObj, and check if any of the given properties of the object contains val into the same structure of the filtered objects.

That, of course, should work into deeper levels of the object, so I could define the searchObj as follows and still be able to get if the filtered object has that structure inside:

vm.searchObj = {
    parent: {
        name: 'Item'
    }
};

In this case, it should show those items where its parent.name property contains the word 'Item'.

If you need more information, please, let me know!

Thank you!

3

There are 3 best solutions below

3
On BEST ANSWER

If I understand correctly, an object being searched (we'll call it target) is a match if:

  • both searchObj and target share at least one property name at the same level

  • The value of target's property is equal to or contains the value of searchObj's property

The code below (which includes your edits) should do what you need. I'll leave it to you to fill in any edge cases (checks for hasOwnProperty, comparison of different types, etc.). I think filtering like this should only happen when necessary, like when a user types. Creating an Angular filter out of this could be too expensive, since it runs on each digest cycle:

function objectContains(searchObj, target) {
    for (var property in searchObj) {
        var searchVal = searchObj[property];
        var targetVal = target[property];

        if (isObject(searchVal) && isObject(targetVal)) {
            if(objectContains(searchVal, targetVal)){
                return true;
            }
        }

        if (
            !isObject(searchVal) && !isObject(targetVal) && 
            !isUndefinedOrNull(targetVal) && 
            !isUndefinedOrNull(searchVal) &&
            targetVal.toString().indexOf(searchVal.toString()) !== -1
        ) {
            return true;
        }
    }

    return false;
}

function isUndefinedOrNull(x) {
    return typeof x === "undefined" || x === null;
}

function isObject(x) {
    return typeof x === "object";
}

var filtered = list.filter(function(item) {
    return objectContains(searchObj, item);
});
0
On

So, taking as the base the code by Frank Modica (thank you), I've come up with some editions on its compare function to look up not only for the first property, but if this does not match, keep up searching. This is the code modified:

function objectContains(searchObj, target) {
    for (var property in searchObj) {
        var searchVal = searchObj[property];
        var targetVal = target[property];

        if (isObject(searchVal) && isObject(targetVal)) {
            if(objectContains(searchVal, targetVal)){
                return true;
            }
        }

        if (
            !isObject(searchVal) && !isObject(targetVal) && 
            !isUndefinedOrNull(targetVal) && 
            !isUndefinedOrNull(searchVal) &&
            targetVal.toString().indexOf(searchVal.toString()) !== -1
        ) {
            return true;
        }
    }

    return false;
}

In case it matches, it returns true because it needs only one match to be what we are looking for. In case it does not match, it keeps going.

We check for the second condition not to be an object neither of the values because they turn to [object OBJECT] when we apply toString(), so it returns true always. This way, if it's an object, it will ignore it (no need to do further checkings because we already done that in the first if).

Thank you Frank because I couldn't come up with this!

Now, it works perfectly!

0
On

Try this way. Use true property on filter to get exactly what you search!

<li data-ng-repeat="[...] | filter: Ctrl.searchObj:true | orderBy: Ctrl.orderBy">
...
</li>