Help with angular material table

Hi guys, I’m dealing with this for a long time and I can’t find a good solution for my angular + angular material side project.

With time as the requirements were more clear I’ve found that I have basically the same table functionality in three different page components. And with new requirements I have to make two new pages with the same table functionality. So I wanted to make one reusable table, as every sane programmer should, right?

The problem is with angular material filterPredicate. Filter can contain only string. But I have two sources of filter value for one table. I made an ugly picture for you:


In yellow is a type filter. User can click on one or more types which are in said table and the table will display only those rows.

Then, there is the purple filter. It has a select to choose a category to filter by and input field for inserting the value. Based on the selected type the input field is either text or select.

User can also choose to filter without selected filterBy, in that case the filtering behaves like in the default filter predicate, so it combines all values from the row and searches in created string.

If he selects filterBy, filterPredicate searches only in that column.

User can combine both filters, so he can select few types in the yellow area and input search text (and possibly filterBy category) in the purple area.

My filterPredicate looks like this:
public filterByWh = '**|*filter**by**wh*|**'; // no upper case letters, because it will be sanitazied in filter <- as you can see, if I have this terrible thing in there, I’m doing something very wrong

this.dataSource.filterPredicate = (row: any, filter: string) => {
      console.warn('selectedFromWh', this.picka);
      let matchFound = false;
      if (this.picka.types.length) {
        if (!this.picka.types.includes(row[this.targetType])) {
          return matchFound;
        }

        if (
          this.picka.technicianId && (!row.assignedTo || row.assignedTo && row.assignedTo.uid !== this.picka.technicianId)
        ) {
          return matchFound;
        }
      }
      // when filtering only by WH types, which is the case when this condition is true,
      // we have no need to check any futher, because it's a match
      if (filter === this.filterByWh) {
        return true;
      }
      // if user selected filter type, we search only in selected column
      if (this.selectedFilter) {
        // look into column definition, if selected column is of type date, we will later convert it from date value to string
        const isDate = this.columnsDefinition[this.selectedFilter] && this.columnsDefinition[this.selectedFilter].type === 'date';
        // check if it's id value (like technician and his uid)
        const isId = this.columnsDefinition[this.selectedFilter] && this.columnsDefinition[this.selectedFilter].isId;
        // return correct value based on the selected type
        const columnValue = isDate
          ? row[this.selectedFilter] ? this.dateService.getValidDateStringFormatFromDate(row[this.selectedFilter]) : ''
          : row[this.selectedFilter] && isId
            ? row[this.selectedFilter].uid ? row[this.selectedFilter].uid : ''
            : (row[this.selectedFilter] ? row[this.selectedFilter] : '').toString().trim().toLowerCase();
        const filterValue = isId
          ? filter
          : (isDate ? this.dateService.getPartialValidDateFormatFromString(filter) : filter.trim().toLowerCase());

        matchFound = (matchFound || columnValue.indexOf(filterValue) !== -1);
      } else {
        for (const column of this.displayedColumns) {
          if (column in row && row[column]) {
            const isDate = this.helper.isDateColumn(column);
            const columnValue = isDate
              ? this.dateService.getValidDateStringFormatFromDate(row[column])
              : row[column].toString().trim().toLowerCase();
            const filterValue = isDate
              ? this.dateService.getPartialValidDateFormatFromString(filter)
              : filter.trim().toLowerCase();

            matchFound = (matchFound || columnValue.indexOf(filterValue) !== -1);
          }
        }
      }

      return matchFound;
    };

Yes, the code is absolutely terrible and I hate it, any suggestions would be welcomed.

BUT, it works, in every page. The problem is, when I want to make a common table component. The the filterPredicate breaks. Because the value from the yellow area doesn’t initiate the filter change (if I understand it correctly).

So, my questions are:

  1. How to work with filter predicate, if it has multiple sources of filter value.
  2. How to make it so that I can have one common table with functioning filterPredicate

Should I make a table service and from page components send an object which contains values from both (yellow and purple) filters and listen on them in table component? That’s what I’ve though of last night in a shower, but something doesn’t seem right about it.

It’s blocking me in creating the two new pages because I don’t want to just copy the code again. And it’s hurting me because I hate how the code looks.

Please help a person with tiny brain. Thank you.

1 Like

Hello Martin,
It’s quite difficult to help you with so few context. I’m used to work with MatTable, in a templated way. Injecting cells templates, dynamic columns etc.
But I’m not used to work with filterPredicate. I’m getting data live from server, and I filter thanks to the server.
Nevertheless, I’ll try to help you. I guess “this.dataSource.filterPredicate” is in your MatTable Component. If it is, you have to manage all the cases, which is not what you want.
I think you should manage the dataSource outside your MatTable Component, which shouldn’t be aware of your filters, it should just display data.
You could try to set the data of your MatTable thanks to a customPipe, filtering your array. This Pipe could get your filters as parameters.
I suggest you also to look at ngx-pipes, especially the filterByPipe one, ngx-pipe is a great lib of pipe utilities.
Good luck :wink:

1 Like

Hi Benoit.
Sorry for a lack of context, I thought I gave too much of it.
I’m using firebase as a backend, so every call costs me. Filtering on client side looks like a better option for me. I display table based on some parameters, there isn’t a crazy amount of fields (in most cases). That’s why I chose client side filtering.

Thank you for your suggestion, I’m gonna try the pipe :slight_smile: