Technical

Custom Filters for Mat-Table in Angular

Filter can be planted in Web Pages for narrowing down the list based on a search criteria.

In Angular, using the Google Material Toolkit, it is possible to create Lists using Mat-Table. In these Mat-Table, it is possible to provide a default Filter taking the code straight from the Code Sample provided in the Material Documentation. (Please see the section on “Filtering” on this page as provided in the link)

The default Filter on Mat-Table works by searching for specific text in the contents available in the Mat-Table.

However, it is possible to create Custom Filters which can be used to Filter based on any combination of search criteria.

Programming the Default Filter on Mat-Table

Before we discuss the Custom Filters, we will see the code required for creating the default Filter.

To help understanding of the concept and code, let us see a working example of what we will try to program. The video below provides an example. All the contents are not pertaining to the Filters. However, look for the sections of the Video which shows the working of the Filters – Default Filter and the Custom Filters.

In the Video, look the sections where we search for a specific Currency. These code uses the Default Filter.

In the Video, look for the sections where we try to look for specific kinds of records based on criteria like Error or Not Error, New Records or Modified Records, etc. These sections use code for Custom Filters.

An Example HTML Code for creating Mat-Table

These contents should be in the .HTML File.

          <div class="mat-elevation-z8">
              <table mat-table [dataSource]="listData" class="mat-elevation-z8" matSort width="100%">

                  <!--- Note that these columns can be defined in any order.
                          The actual rendered columns are set as a property on the row definition" -->

                  <!-- Country Code Column -->
                  <ng-container matColumnDef="countryCode">
                      <th mat-header-cell *matHeaderCellDef mat-sort-header> Country Code </th>
                      <td mat-cell *matCellDef="let element" align="left"> {{element.countryCode}} </td>
                  </ng-container>

                  <!-- Country Name Column -->
                  <ng-container matColumnDef="countryName">
                      <th mat-header-cell *matHeaderCellDef mat-sort-header> Name </th>
                      <td mat-cell *matCellDef="let element" align="left"> {{element.countryName}} </td>
                  </ng-container>

                  <!-- Last Update Date Column -->
                  <ng-container matColumnDef="lastUpdateDate">
                    <th mat-header-cell *matHeaderCellDef hidden> Last Update Date </th>
                    <td mat-cell *matCellDef="let element" align="left" hidden> {{element.lastUpdateDate}} </td>
                  </ng-container>

                  <!-- Edit Column -->
                  <ng-container matColumnDef="edit">
                    <th mat-header-cell *matHeaderCellDef>
                    </th>
                    <td mat-cell *matCellDef="let row">
                        <a mat-button matTooltip="Edit" [routerLink]="['/country/edit', row.countryCode]" *ngIf="isUserAuthenticated"><mat-icon>create</mat-icon></a>
                    </td>
                  </ng-container>

                  <!-- Delete Column -->
                  <ng-container matColumnDef="delete">
                    <th mat-header-cell *matHeaderCellDef>
                    </th>
                    <td mat-cell *matCellDef="let row">
                        <button mat-button matTooltip="Delete" color="warn" (click)="onDelete(row.countryCode, row.lastUpdateDate)" *ngIf="isUserAuthenticated"><mat-icon>clear</mat-icon></button>
                    </td>
                  </ng-container>

                  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
                  <tr mat-row *matRowDef="let row; columns: displayedColumns;" (click)="selectRow(row)" [ngClass]="{ 'selected': row === selectedRow }"></tr>
              </table>

              <mat-paginator [pageSizeOptions]="[7, 15, 20]" showFirstLastButtons></mat-paginator>
          </div>

HTML Code to include for setting up Default Filter

These contents should be in the .HTML File.

If you are placing the Mat-Table on a Mat-Card, it could be good idea to place both the Mat-Table code and the code for the Filter display on the same Mat-Card.

Typically, it could be a good idea to place the Filter code above the Mat-Table code so that the Filter conditions appear above the List of the value. However, this entirely depends on your GUI Design.

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

Code to include in the .TS File

Variable Declarations

  dataList: Country[] = [];

  displayedColumns: string[] = [
    'countryCode',
    'countryName',
    'lastUpdateDate',
    'edit',
    'delete'];

  listData = new MatTableDataSource<Country>(this.dataList);

Method to include

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

Programming Custom Filters on Mat-Table

The code in the previous section is all that is needed for coding the default Filter. For Custom Filters, we examine code for a few different scenarios in the sections below.

For programming the Custom Filters, we need to have suitable Data Structures on which these Filters will work. Given below is an example of the Data Structure based on which the remaining section codes will be explained.

export interface UploadData {
  dataRecord: Country;
  recordStatus: number;
  updateStatus: number;
  record: string;
  recordError: string;
  updateErrorMessage: {
    message: string;
    details: string;
  };
}

In the above Data Structure, we will use the fields “recordStatus” and “updateStatus” for setting the filters. Both these fields take a numeric value (as an Integer) and each value indicates a particular state.

Programming Custom Filter controlled by CheckBox

See the section of the Video where we Filter records based on whether we want to see only Invalid Records or not.

Code required in .HTML File

Setting up the Check Box for providing the criteria for Filtering

                        <mat-card>
                          <table>
                            <tr>
                              <td align="right" colspan=1>
                                  <mat-checkbox color="warn" #uploadOnlyErrors [(checked)]="isUploadOnlyErrorsChecked" (change)="onUploadOnlyErrorsChange($event)" *ngIf="numberOfInvalidRecords > 0">Only Invalid Records</mat-checkbox>
                              </td>
                            </tr>
                          </table>
                        </mat-card>

Code for setting up the corresponding Mat-Table

                        <div class="mat-elevation-z8">
                            <!-- <table id="DataPreview" mat-table [dataSource]="uploadData" class="mat-elevation-z8" width="100%"> -->
                            <table id="DataPreview" mat-table [dataSource]="uploadData" multiTemplateDataRows class="mat-elevation-z8" width="100%">

                                <!--- Note that these columns can be defined in any order.
                                        The actual rendered columns are set as a property on the row definition" -->

                                <!-- <ng-container matColumnDef="{{column}}" *ngFor="let column of displayedColumns">
                                  <th mat-header-cell *matHeaderCellDef> {{column}} </th>
                                  <td mat-cell *matCellDef="let element"> {{element[column]}} </td>
                                </ng-container> -->

                                <!-- Country Code Column -->
                                <ng-container matColumnDef="countryCode">
                                    <th mat-header-cell *matHeaderCellDef> Country Code </th>
                                    <td mat-cell *matCellDef="let element" align="left"> {{element.dataRecord.countryCode}} </td>
                                </ng-container>

                                <!-- Country Name Column -->
                                <ng-container matColumnDef="countryName">
                                    <th mat-header-cell *matHeaderCellDef> Name </th>
                                    <td mat-cell *matCellDef="let element" align="left"> {{element.dataRecord.countryName}} </td>
                                </ng-container>

                                <!-- Record Status Column -->
                                <ng-container matColumnDef="recordStatus">
                                  <th mat-header-cell *matHeaderCellDef> Record Status </th>
                                  <td mat-cell *matCellDef="let row">
                                      <button mat-button color="primary" *ngIf="row.recordStatus === I_NEW_RECORD_INDICATOR"><mat-icon>add_circle</mat-icon></button>
                                      <button mat-button color="primary" *ngIf="row.recordStatus === I_MODIFIED_RECORD_INDICATOR"><mat-icon>create</mat-icon></button>
                                      <button mat-button color="warn" *ngIf="row.recordStatus === I_INVALID_RECORD_INDICATOR"><mat-icon>clear</mat-icon></button>
                                      <button mat-button color="basic" *ngIf="row.recordStatus === I_UNCHANGED_RECORD"><mat-icon>cloud_off</mat-icon></button>
                                  </td>
                                </ng-container>

                                <!-- Expanded Content Column - The detail row is made up of this one column that spans across all columns -->
                                <ng-container matColumnDef="expandedDetail">
                                    <td mat-cell *matCellDef="let element" [attr.colspan]="displayedColumns.length">
                                      <div class="example-element-detail"
                                          [@detailExpand]="element == expandedElement ? 'expanded' : 'collapsed'" *ngIf="element.recordStatus === I_INVALID_RECORD_INDICATOR">
                                        <table width="100%">
                                          <tr>
                                            <td align="left">Original Record: <b>{{element.record}}</b></td>
                                          </tr>
                                          <tr>
                                            <td align="left"><p class="FailedMessage">Error: <b>{{element.recordError}}</b></p></td>
                                          </tr>
                                        </table>
                                      </div>
                                    </td>
                                </ng-container>

                                <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
                                <tr mat-row *matRowDef="let row; columns: displayedColumns;"
                                      class="example-element-row"
                                      [class.example-expanded-row]="expandedElement === row"
                                      (click)="expandedElement = row"
                                  >
                                </tr>
                                <tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="example-detail-row"></tr>
                            </table>

                            <mat-paginator #uploadPaginator [pageSizeOptions]="[7, 15, 20]" showFirstLastButtons></mat-paginator>
                        </div>

Code required in the .TS File

  onUploadOnlyErrorsChange($event) {
    this.uploadData.filterPredicate = (data: UploadData, filter: string) =>
                            ($event.checked) ? data.recordStatus === this.I_INVALID_RECORD_INDICATOR
                                            : (data.recordStatus === this.I_INVALID_RECORD_INDICATOR ||
                                              data.recordStatus === this.I_MODIFIED_RECORD_INDICATOR ||
                                              data.recordStatus === this.I_NEW_RECORD_INDICATOR ||
                                              data.recordStatus === this.I_UNCHANGED_RECORD
                                              );
    this.uploadData.filter = '1';
    if (this.uploadPaginator) { this.uploadPaginator.firstPage(); }
  }

Programming Custom Filter controlled by RadioButton

See the section of the Video where we Filter records based on the nature of the Record whether we want to see only New Records or Modified Record or both New and Modified Records or All the Records.

Code required in .HTML File

Setting up the Radio Buttons for providing the criteria for Filtering

                        <mat-card>
                          <table>
                            <tr>
                                <td align="center" colspan=2>
                                    <mat-radio-group>
                                      <mat-radio-button color="primary" value="1" checked (change)="onPreviewChange($event)">All</mat-radio-button>
                                      <mat-radio-button color="primary" value="2" (change)="onPreviewChange($event)" *ngIf="numberOfRecordsToAdd > 0">Only New</mat-radio-button>
                                      <mat-radio-button color="primary" value="3" (change)="onPreviewChange($event)" *ngIf="numberOfRecordsToUpdate > 0">Only Modified</mat-radio-button>
                                    </mat-radio-group>
                                </td>
                            </tr>
                          </table>
                        </mat-card>

Code for setting up the corresponding Mat-Table

                        <div class="mat-elevation-z8">
                            <table id="uploadPreview" mat-table [dataSource]="toUploadData" class="mat-elevation-z8" width="100%">

                                <!--- Note that these columns can be defined in any order.
                                        The actual rendered columns are set as a property on the row definition" -->

                                <!-- Country Code Column -->
                                <ng-container matColumnDef="countryCode">
                                    <th mat-header-cell *matHeaderCellDef> Country Code </th>
                                    <td mat-cell *matCellDef="let element" align="left"> {{element.dataRecord.countryCode}} </td>
                                </ng-container>

                                <!-- Country Name Column -->
                                <ng-container matColumnDef="countryName">
                                    <th mat-header-cell *matHeaderCellDef> Name </th>
                                    <td mat-cell *matCellDef="let element" align="left"> {{element.dataRecord.countryName}} </td>
                                </ng-container>

                                <!-- Record Status Column -->
                                <ng-container matColumnDef="recordStatus">
                                  <th mat-header-cell *matHeaderCellDef> Record Status </th>
                                  <td mat-cell *matCellDef="let row">
                                      <button mat-button color="primary" *ngIf="row.recordStatus === I_NEW_RECORD_INDICATOR"><mat-icon>add_circle</mat-icon></button>
                                      <button mat-button color="primary" *ngIf="row.recordStatus === I_MODIFIED_RECORD_INDICATOR"><mat-icon>create</mat-icon></button>
                                      <button mat-button color="warn" *ngIf="row.recordStatus === I_INVALID_RECORD_INDICATOR"><mat-icon>clear</mat-icon></button>
                                      <button mat-button color="basic" *ngIf="row.recordStatus === I_UNCHANGED_RECORD"><mat-icon>cloud_off</mat-icon></button>
                                  </td>
                                </ng-container>

                                <!-- Update Status Column -->
                                <ng-container matColumnDef="updateStatus">
                                  <th mat-header-cell *matHeaderCellDef> Update Status </th>
                                  <td mat-cell *matCellDef="let row">
                                      <button mat-button *ngIf="row.updateStatus === I_UPDATE_STATUS_SUCCESSFUL"><mat-icon>mood</mat-icon></button>
                                      <button mat-button color="warn" *ngIf="row.updateStatus === I_UPDATE_STATUS_FAILED"><mat-icon>error</mat-icon></button>
                                      <button mat-button *ngIf="row.updateStatus === I_YET_TO_UPDATE"><mat-icon>notifications_active</mat-icon></button>
                                      <button mat-button *ngIf="row.updateStatus === I_NO_ACTION"><mat-icon>notifications_none</mat-icon></button>
                                  </td>
                                </ng-container>

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

                            <mat-paginator #toUploadPaginator [pageSizeOptions]="[7, 15, 20]" showFirstLastButtons></mat-paginator>
                        </div>

Code required in the .TS File

  onPreviewChange($event) {
    if (parseInt($event.value, 10) === 1) {
      this.toUploadData.filterPredicate = (data: UploadData, filter: string) =>
                                  (data.recordStatus === this.I_MODIFIED_RECORD_INDICATOR ||
                                    data.recordStatus === this.I_NEW_RECORD_INDICATOR);
    } else if (parseInt($event.value, 10) === 2) {
      this.toUploadData.filterPredicate = (data: UploadData, filter: string) =>
                                  (data.recordStatus === this.I_NEW_RECORD_INDICATOR);
    } else {
      this.toUploadData.filterPredicate = (data: UploadData, filter: string) =>
                                  (data.recordStatus === this.I_MODIFIED_RECORD_INDICATOR);
    }
    this.toUploadData.filter = '1';
    if (this.toUploadPaginator) { this.toUploadPaginator.firstPage(); }
  }

Advertisements

This site uses Akismet to reduce spam. Learn how your comment data is processed.