user2024080
user2024080

Reputation: 5101

How to filter an array based on the grand child array's values?

I am trying to filter the parent array based on the grand child array's values, but my filtering approach doesn't work well and the UI is not getting updated.

Here is my filter function.

filterIt($event) {
    this.filter.RegCategoryName = $event.target.value;
    this.dataObject = staticData.filter(value => 
        value.List.filter(ch => ch.RegistrationCategory.filter(gChild =>
            Object.keys(this.filter).every(ob => 
                String(gChild[ob]).toLowerCase().includes(String(this.filter[ob]).toLowerCase())
            )
        )).length
    )
}

Live Demo

Expectation:

When the user types new Course, it should only show DP Programme in Programme column.

Upvotes: 2

Views: 2397

Answers (1)

nash11
nash11

Reputation: 8660

You can achieve this by using a combination of map, filter and some Array methods. Use the spread operator ... so that your original array does not get updated in the child objects.

filterIt($event) {
    this.filter.RegCategoryName = $event.target.value;
    this.dataObject = staticData.filter(value => {
        const data = { ...value };
        data.List = data.List.map(ch => {
            const list = { ...ch };
            list.RegistrationCategory = list.RegistrationCategory.filter(gChild => {
                return gChild.RegCategoryName.toLowerCase().indexOf(this.filter.RegCategoryName.toLowerCase()) !== -1
            });
            return list;
        });
        return data.List.some(list => !!list.RegistrationCategory.length);
    });
}

Note: Avoid using variable names with the first letter capitalized.

Here is a working example on StackBlitz.

Explanation:

The structure used here is pretty complicated so I guess I'll start from inside out. Let's start out with the innermost filter.

list.RegistrationCategory = list.RegistrationCategory.filter(gChild => {
    return gChild.RegCategoryName.toLowerCase().indexOf(this.filter.RegCategoryName.toLowerCase()) !== -1
});

The filter() method creates a new array with all elements that pass the test implemented by the provided function.

This one should be clear enough. The condition used here is the same one used in Angular Material's mat-table filter. This will filter if RegCategoryName contains the search term. I say contain because this will match new Course even if your search term is ew. If this is not what you desire, you can use startsWith instead. This will only match if your search term is new and not ew.

list.RegistrationCategory = list.RegistrationCategory.filter(gChild => {
    return gChild.RegCategoryName.toLowerCase().startsWith(this.filter.RegCategoryName.toLowerCase())
});

Moving on to the first parent array List. We do not want to filter or change the objects in here. Instead, we just want the same object but with the new filtered RegistrationCategory. This fits well for a map since map returns an array with the same number of objects(with some modification to the elements within the object if desired).

The map() method creates a new array with the results of calling a provided function on every element in the calling array.

So in our map we will update RegistrationCategory using the filter we created above. We cannot modify ch directly since that would mutate the original object. So instead we will shallow clone ch using the spread syntax into a variable list, modify RegistrationCategory in our new list variable using the above filter and return the object. This now gives us the List array as is by just updating RegistrationCategory in each object.

Now for the final piece, we need to filter through staticData. As we did in the map, we will use the spread syntax here again as we do not want to update the original object. data.List will now have each RegistrationCategory filtered thanks to our innermost filter, so we just need to return a true/false value based on whether the List array has any object whose RegistrationCategory length is more than 0. We can use some for this.

The some() method tests whether at least one element in the array passes the test implemented by the provided function. It returns a Boolean value.

So if any RegistrationCategory in the List array has length more than 0(since that's all we care about, whether any RegistrationCategory is still present after our innermost filter has filtered List), it returns true and is added to the filtered dataObject.

This way we get each object in staticData whose List array contains an object where RegistrationCategory has some data.

I hope this makes it clear why you were having issues with your code and why you cannot use a filter for all three arrays.

Upvotes: 2

Related Questions