Search code examples
angularangular-materialspecial-charactersdiacriticsmat-table

How can I filter a MatTable with accents/diacritics?


I want to filter the names of a table regardless of whether the user has entered any accents or not.

For example:

If the user enters "hydrogen", the result should bring "Hydrôgen" as it is in the table.

I use Angular 8.1.3 and Angular Material 8.0.1

Here is my code:

app.component.html:

<mat-form-field>
  <input matInput (keyup)="applyFilter($event.target.value)"  (focus)="setupFilter('name')" placeholder="Filter">
</mat-form-field>

<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
...

my interface:

export interface PeriodicElement {
  name: string;
  position: number;
  weight: number;
  symbol: string;
}

app.component.ts:

const ELEMENT_DATA: PeriodicElement[] = [
  {position: 1, name: 'Hydrôgen', weight: 1.0079, symbol: 'H'},
  {position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'},
  {position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'},
  {position: 4, name: 'Béryllium', weight: 9.0122, symbol: 'Be'},
  {position: 5, name: 'Bóron', weight: 10.811, symbol: 'B'},
  {position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'},
  {position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N'},
  {position: 8, name: 'ÔLxygen', weight: 15.9994, symbol: 'O'},
  {position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F'},
  {position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne'},
];

displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
dataSource = new MatTableDataSource(ELEMENT_DATA);

  applyFilter(filterValue: string) {
    this.dataSource.filter = filterValue.trim().toLowerCase();
  }

  setupFilter(column: string) {
    this.dataSource.filterPredicate = (d: any, filter: string) => {
      const textToSearch = d[column] && d[column].toLowerCase() || '';
      return textToSearch.indexOf(filter) !== -1;
    };
  }

  public removeAccents(str: string): string {
    return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
  }

I expect when user input "bo" show in table 2 lines those containing "Carbon" and "Bóron".


Solution

  • This is the code that Angular material resorts to when you use filtering in a mat-table

    filterPredicate: ((data: T, filter: string) => boolean) = (data: T, filter: string): boolean => {
      // Transform the data into a lowercase string of all property values.
      const dataStr = Object.keys(data).reduce((currentTerm: string, key: string) => {
        // Use an obscure Unicode character to delimit the words in the concatenated string.
        // This avoids matches where the values of two columns combined will match the user's query
        // (e.g. `Flute` and `Stop` will match `Test`). The character is intended to be something
        // that has a very low chance of being typed in by somebody in a text field. This one in
        // particular is "White up-pointing triangle with dot" from
        // https://en.wikipedia.org/wiki/List_of_Unicode_characters
        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();
    
      return dataStr.indexOf(transformedFilter) != -1;
    }
    

    Let's go ahead and modify this filterPredicate() to what you need. You have already shown how to remove the accents and diacritics in your code. We'll use this in our new filterPredicate.

    this.dataSource.filterPredicate = (data: PeriodicElement, filter: string): boolean => {
      const dataStr = Object.keys(data).reduce((currentTerm: string, key: string) => {
        return (currentTerm + (data as { [key: string]: any })[key] + '◬');
      }, '').normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
    
      const transformedFilter = filter.trim().normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
    
      return dataStr.indexOf(transformedFilter) != -1;
      }
    }
    

    We remove accents/diacritics from both the values in the table as well as the filter value so that even if the user happens to enter characters which have accents/diacritics, the filter will continue to filter correctly.

    Here is a working example on StackBlitz.