Atticus29
Atticus29

Reputation: 4412

Angular Material mat-table not returning any results upon filter

I have attempted to re-create a mat-table using the Angular Material package. The mat-table examples have recently been massively improved, which is amazing. They provided this example here with both sort and filter features.

I have attempted to reproduce this within the context of my own app, using dummy data in a minimal example on stackblitz here.

The table displays and is populated with data, but as soon as I type more than one letter, the filter method seems to incorrectly be removing all entries, even ones that it should not.

One difference that I can think of here is that in my example, I'm using a class in my declaration of the dataSource: dataSource: MatTableDataSource<Match>;, whereas in the example provided by the Angular people, it's an interface: dataSource: MatTableDataSource<UserData>;.

This is just as likely a red herring, though, as I tried playing with an interface instead with no success. Any help here is very much appreciated.

Upvotes: 1

Views: 7283

Answers (4)

nash11
nash11

Reputation: 8650

As mentioned in the other answers the filter function only filters one level deep. In your question though, I don't see a reason for your data being one level deep in the first place. In your template, you are only displaying items from matchDeets, so why not just use matchDeets as your dataSource. This will make your filter function work again.

In your component, simply change the dataSource to the following.

dataSource: MatTableDataSource<MatchDetails>;

// Other code goes here

this.dataSource = new MatTableDataSource(matchArray.map(match => match.matchDeets));

This change will be immutable so matchArray will still contain the other data from your class. Since your template doesn't seem to care about them, we shall just pass to the dataSouce what we need, i.e., matchDeets.

Now in your template, you just need to update the display values from {{entry.matchDeets.rank}} to {{entry.rank}}, {{entry.matchDeets.weightClass}} to {{entry.weightClass}} and so on and so forth.

Your filter function will now work. Here is a working example on StackBlitz.

Upvotes: 1

terahertz
terahertz

Reputation: 3491

In the official Angular stackblitz example, the dataSource has an array of objects that is 1 level deep, as evident by doing a console.log(this.dataSource.filteredData):

enter image description here

Your dataSource, however, nests another level of object, twice, in an Match object (i.e. MatchDetails and MoveInVideo objects are nested inside a Match object):

enter image description here

As such, the filter function provided will not work for you because it only checks the first-level of object (Match) against the filter value. In fact, it is working correctly by returning [] because none of the values in your Match objects matches the filter value. If you search for 'originalFilterPosterId', it will return all objects because all Match Objects contain this value.

You can fix this by setting a generic filterPredicate in ngOnInit(), before calling the applyFilter() event handler. This generic function will be able to filter through the nested objects. All credits to @Sagar Kharche and @mighty_mite Stackover answer source.

ngOnInit(){
  this.dataSource.paginator = this.paginator;
  this.dataSource.sort = this.sort;

  //set a new filterPredicate function
  this.dataSource.filterPredicate = (data, filter: string) => {
    const accumulator = (currentTerm, key) => {
      return this.nestedFilterCheck(currentTerm, data, key);
    };
    const dataStr = Object.keys(data).reduce(accumulator, '').toLowerCase();
    // Transform the filter by converting it to lowercase and removing whitespace.
    const transformedFilter = filter.trim().toLowerCase();
    return dataStr.indexOf(transformedFilter) !== -1;
  }
}

//also add this nestedFilterCheck class function
nestedFilterCheck(search, data, key) {
  if (typeof data[key] === 'object') {
    for (const k in data[key]) {
      if (data[key][k] !== null) {
        search = this.nestedFilterCheck(search, data[key], k);
      }
    }
  } else {
    search += data[key];
  }
  return search;
}

You don't need to change anything in your applyFilter() function.

Forked Stackblitz Example ⚡

Upvotes: 9

Gokhan Kurt
Gokhan Kurt

Reputation: 8277

Normally, the filter works by serializing the first level data of the object to a string then doing a quick check for string inclusion. This does not work for deep objects. So you can't rely on default filtering and need to provide a filterPredicate.

In the documents it says

To override the default filtering behavior, a custom filterPredicate function can be set which takes a data object and filter string and returns true if the data object is considered a match.

Example that filters only by location:

    this.dataSource.filterPredicate = (match: Match, filter: string) => {
      return match.matchDeets.location.toLowerCase().includes(filter);
    };

Upvotes: 3

JWP
JWP

Reputation: 6963

To allow any type of array, the MatTableDataSource should look like this in the model.

  dataSource = new MatTableDataSource<any>();

To clear the filter:

this.dataSource.filter="";

To see what's to be displayed:

this.dataSource.data

To start the filtering process from an input element hook up keydown and keyup

<input id='search' #search  matinput type='text' (keydown)="onKeyDown(search,$event)" (keyup)='onSearchChanged(search, $event)' />

//this catches the backspace
onKeyDown(searchInput, event) {
      if (event.key === "Backspace") {
         this.filter = this.dataSource.filter = searchInput.value;
         this.cdf.detectChanges();
      }
   }

//this changes the filter as they type in the input field
onSearchChanged(search, event) {
      this.filter = search.value;
      this.dataSource.filter = this.filter;
      this.cdf.detectChanges();
   }

If nothing is showing you must make sure your columnheaders and columnIDs are all set.

Upvotes: 0

Related Questions