2

I have a table within my Angular app that displays user data within its rows. I want to be able to filter this table using an Array of strings that represent user emails. If the user's email exists in the array, then I don't want the user to be displayed in the table.

I've had a look into filtering Angular material tables, specifically here where they use a single string to filter the table. All the examples I seem to find also involve using a single string, for example from the user specifying a filter string via an input.

However, I can't find any solutions to using an array of strings to filter the data. Do I need to concatenate the whole array into a string and then pass this into the filterPredicate property?

    applyAddedUserFilter() {
        this._usersService.addedUsers$.subscribe((addedUsers: AddedUser[]) => {
          let addedUserEmails: string[] = addedUsers.map(({ email }) => email);

          this.users.filterPredicate = (user: GoogleUser, filter: string) => { //<-- What exactly do I need to do here to use my string[] to filter my table's results?
            //Unsure what I need to do here.
          }
        });
      }

Also once I have the filter predicate set, where do I need to call this method. Is it enough to be able to call the method once the table has been constructed?

Jake12342134
  • 1,539
  • 1
  • 18
  • 45
  • maybe try something like this: ```let filteredData = this.users.filter(user => !(addedUserEmails.indexOf(user.email) > -1));``` You can then add it like this --> ```this.dataSource.data = filteredData;``` – sagat Aug 06 '19 at 10:48
  • yes. I would suggest to do this instead of filterpredicate and directly filter the array and set it to the datasource.data. This should work. – sagat Aug 06 '19 at 10:53
  • @sagat Can you edit the original post to show me how you're implementing this? If I try doing `this.users.filter(user => !(addedUserEmails.indexOf(user.email) > -1));` I get a compiler error. – Jake12342134 Aug 06 '19 at 10:54
  • let m,e understanfd first --> addedUserEmails is the array with the filter conditions, means, if the email is in there you dont want to show the user in the table. What is the users array called, that u want to filter. Can u post the whole component.ts – sagat Aug 06 '19 at 10:55
  • `this.users` is `users: MatTableDataSource = new MatTableDataSource([]);`, all I want to do is filter the data so that if any emails in the data source exist in the `addedUserEmails` they are not displayed in the table – Jake12342134 Aug 06 '19 at 10:57
  • okay i made a stackblitz. Answering in a sec – sagat Aug 06 '19 at 11:04
  • Mate it might be that u have to do this MatTableDataSource, u didnt specify a collection of users but a single User. – sagat Aug 06 '19 at 12:01

2 Answers2

1

I would do it like this:

applyAddedUserFilter() {
    this._usersService.addedUsers$.subscribe((addedUsers: AddedUser[]) => {
      let addedUserEmails: string[] = addedUsers.map(user => user.email);

      this.dataSource.data = this.users.filter((user: GoogleUser) => !(addedUserEmails.indexOf(user.email) > -1));
    });
  }

Here is a stackblitz: Material Table Filter

sagat
  • 1,351
  • 12
  • 15
  • `Cannot invoke an expression whose type lacks a call signature. Type 'String' has no compatible call signatures`. That's the compiler error I get when attempting to use that line of code. – Jake12342134 Aug 06 '19 at 11:18
  • Did u check the stackblitz? Couldbu Provide your while component Code? – sagat Aug 06 '19 at 11:42
  • This is because ur GoogleUser Type and String are not the same types. – sagat Aug 06 '19 at 11:55
1

This is a late answer but surely will help for those who are interested.

You can use mat-table-filter for filtering. It supports array filtering too. It saves you from implementing filtering boilerplate including debounce etc.

The only thing you need to do is adding matTableFilter directive to your material table and binding exampleEntity with a representation of what you have as an item inside your datasource.

 <table mat-table matTableFilter [dataSource]="dataSource"
 [exampleEntity]="exampleObject"...>

That's all. When you populate the exampleObject's properties, the filter will automatically work just fine with the default debounce support. You can change the debounce time also.

You can take a look at the examples here: https://halittalha.github.io/ng-material-extensions/

I'm sharing the full array filtering source code below. The below example leverages chips component to collect the array contents for filtering.

.html

<mat-table matTableFilter [exampleEntity]="filterEntity" [filterType]="filterType" [dataSource]="dataSource"
  class="mat-elevation-z8">
  <ng-container matColumnDef="category">
    <mat-header-cell *matHeaderCellDef>
      <mat-form-field appearance="outline">
        <input matInput placeholder="Category" [(ngModel)]="filterEntity.category">
      </mat-form-field>
    </mat-header-cell>
    <mat-cell *matCellDef="let element"> {{element.category}} </mat-cell>
  </ng-container>

  <ng-container matColumnDef="brand">
    <mat-header-cell *matHeaderCellDef>
      <mat-form-field appearance="outline">
        <input matInput placeholder="Product Brand" [(ngModel)]="filterEntity.brand">
      </mat-form-field>
    </mat-header-cell>
    <mat-cell *matCellDef="let element"> {{element.brand}} </mat-cell>
  </ng-container>

  <ng-container matColumnDef="availableSizes">
    <mat-header-cell *matHeaderCellDef>

      <mat-form-field class="example-chip-list">
        <mat-chip-list #chipList aria-label="Fruit selection">
          <mat-chip *ngFor="let size of filterEntity.availableSizes" [selectable]="true" [removable]="true"
            (removed)="remove(size)">
            {{size}}
            <mat-icon matChipRemove>cancel</mat-icon>
          </mat-chip>
          <input placeholder="Add Size" [matChipInputFor]="chipList"
            [matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="true"
            (matChipInputTokenEnd)="add($event)">
        </mat-chip-list>
      </mat-form-field>
    </mat-header-cell>
    <mat-cell *matCellDef="let element"> {{element.availableSizes}} </mat-cell>
  </ng-container>

  <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
  <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>

.ts

import { MatTableFilter } from 'mat-table-filter';
import { Component, OnInit } from '@angular/core';
import { MatTableDataSource, MatChipInputEvent } from '@angular/material';
import { COMMA, ENTER } from '@angular/cdk/keycodes';

export class Product {
  category: string;
  brand: string;
  availableSizes: Array<string>;
}

const PRODUCTS: Product[] = [
  {category: 'T-Shirt', brand: 'X', availableSizes: ['S', 'M', 'L', 'XL']},
  {category: 'T-Shirt', brand: 'Y', availableSizes: ['S', 'L', 'XL']},
  {category: 'T-Shirt', brand: 'Z', availableSizes: ['XL']},
  {category: 'Jean', brand: 'X', availableSizes: ['S', 'M', 'L', 'XL']},
  {category: 'Jean', brand: 'Y', availableSizes: ['S', 'M']},
  {category: 'Jean', brand: 'Z', availableSizes: ['S', 'M', 'L']},
  {category: 'Jean', brand: 'B', availableSizes: ['S', 'M', 'L']},
  {category: 'Jacket', brand: 'X', availableSizes: ['S', 'L', 'XL']},
  {category: 'Jacket', brand: 'Z', availableSizes: ['S']},
  {category: 'Pants', brand: 'X', availableSizes: ['S', 'M', 'L', 'XL']},
  {category: 'Pants', brand: 'Y', availableSizes: ['L', 'XL']},
  {category: 'Pants', brand: 'Z', availableSizes: ['S']},
  {category: 'Hat', brand: 'X', availableSizes: ['S', 'M', 'L']},
  {category: 'Skirt', brand: 'X', availableSizes: ['S', 'M', 'L', 'XL']},
  {category: 'Skirt', brand: 'Y', availableSizes: ['S', 'M', 'L']}
 ];

@Component({
  selector: 'app-array-filter',
  templateUrl: './array-filter.component.html',
  styleUrls: ['./array-filter.component.css']
})
export class ArrayFilterComponent implements OnInit {
  filterEntity: Product;
  filterType: MatTableFilter;
  displayedColumns: string[] = ['category', 'brand', 'availableSizes'];
  dataSource;
  readonly separatorKeysCodes: number[] = [ENTER, COMMA];

  add(event: MatChipInputEvent): void {
    const input = event.input;
    const value = event.value;

    if ((value || '').trim()) {
      this.filterEntity.availableSizes.push(value.trim());
    }

    // Reset the input value
    if (input) {
      input.value = '';
    }
  }

  remove(size: string): void {
    const index = this.filterEntity.availableSizes.indexOf(size);

    if (index >= 0) {
      this.filterEntity.availableSizes.splice(index, 1);
    }
  }

  ngOnInit() {
    this.filterEntity = new Product();
    this.filterEntity.availableSizes = new Array<string>(); // DO NOT FORGET TO INIT THE ARRAY
    this.filterType = MatTableFilter.ANYWHERE;
    this.dataSource = new MatTableDataSource(PRODUCTS);
  }
}
talhature
  • 2,246
  • 1
  • 14
  • 28