1
votes

Setup: angular 9.1.7, angular-material 9.2.3, macOS 10.14.6, ngrx-store/ngrx-data/ngrx-entity 9.1.2

Situation:

I have a functioning and working material table with a datasource of type MatTableDataSource obtained from an async HTTP request provided by the ngrx Store. I can filter all the properties of Resource itself with the built-in datasource filter function.

Problem:

In my Material table component, I don't show all Resource properties, some columns display related data that is async loaded for the specific resource property, e.g.: {{(element.orderId | orderContactProperty: 'email') | async}} - the resource (element) has an orderId property, and I also load Orders & Contacts from the same server, when at some point, via a pipe the contact for that order of that resource is loaded and a property ('email') of it is presented. Now this obviously doesn't belong to the datasource of type Resource and is loaded later, meaning it can't be filtered.

What would be the best/simplest solution to also filter these lazy loaded properties that do not belong to the original datasource? Basically, how can I filter what the customer sees displayed in the table?

What could work?:

I looked into the mat-table-exporter extension because there only the visible table data is exported and my thought was to extract the rows with all the visible data, then filter, then update the datasource only with the filtered elements but it seems to be a hack-ish workaround, right?

Also I thought about "extending" the datasource by resolving the related properties before displaying and adding them to the respective row so they are now included to be filtered - wouldn't that also be problematic because then the type Resource is not correct anymore and later when I want to edit/update the resources and save them to the server I would have unwanted properties

But before committing my time into any of these ideas, does anyone have an advice/direction how to best solve this problem? I am surely not the first one to stumble upon it but couldn't find any other questions describing that exact scenario

Unfortunately, I can't provide a stackblitz due to the complexity of it and it being a client project, however I hope this screenshot helps to illustrate my problem: (red asterisk means: not in datasource and can't be filtered, the other ones work fine)

example row

1
Do you need to filter a column that you don't show inside your table? - talhature
@talhature no, unvisible columns get filtered already because they are in the datasource - I need to filter columns that are not in the datasource but are visible in the table from other observables/services and somehow related to the ones in the datasource - hreimer

1 Answers

0
votes

I came across the exact same situation and was surprised not to find a suitable solution for filtering data asynchronously. Since the filterPredicate expects boolean as a return type, it is not possible to merge the Observable from the store selector to the datasource in a clean way.

Therefore, I decided to cache the data from the store (usernames in my case) locally, and extend the default angular material filterPredicate.

This only works, as I already load the data before the table is actually displayed, which limits the use cases of this approach.

filterPredicate: ((data: Data, filter: string) => boolean) = (data: Data, filter: string): boolean => {

    // default angular material filter predicate
    const dataStr = Object.keys(data).reduce((currentTerm: string, key: string) => 
    {
      return currentTerm + (data as {[key: string]: any})[key] + '◬';
    }, '').toLowerCase();

    // Transform the filter by converting it to lowercase and removing whitespace.
    const transformedFilter = filter.trim().toLowerCase();

    const defaultResult = dataStr.indexOf(transformedFilter) !== -1;

    // no need to continue searching after a match
    if (defaultResult) {
      return true;
    }

    // no match found yet, check usernames for matches
    const userIds = this.getUserIds(transformedFilter);
    return userIds.includes(data.userId);

}

This function can then be assigned to the filterPredicate of the datasource, as described in https://material.angular.io/components/table/overview#filtering.

this.dataSource.filterPredicate = this.filterPredicate;

I know that this is not a clean solution either, but it is sufficient for my scenario. Any better approaches would be welcome!