Reputation: 4412
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
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
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)
:
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):
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.
Upvotes: 9
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
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