Server Side search & filter in Angular material table (mat-table)

When you add server side pagination and server side sorting in Angular material table i.e., mat-table, then the searching & filtering should be done on the server side as we do not have total table data.

In this tutorial, we learn how to add server side search & filter functionality in mat-table using simple examples.

To implement server side searching & filtering in Angular mat-table, our API should accept filter parameter i.e., search keyword.

Steps to implement Server Side search & filter in Angular material table.

In client side filtering, we will get all records from the server at a time and filter them using MatTableDataSource’s filter property as explained in previous article

Let’s see how we can add this search & functionality on the server side.

Step 1 : Add a Search Input.

The most basic step is to add search box above mat-table.


<mat-form-field>
  <mat-label>Search & Filter</mat-label>
  <input
    matInput
    [formControl]="searchKeywordFilter"
    placeholder="Title Search"
    #input
  />
</mat-form-field>

Added a matInput search box and assigned a formControl called searchKeywordFilter.

In the component .ts file, initialize the formControl.


searchKeywordFilter = new FormControl();

Step 2: Create a server API which accepts filter search keyword as a parameter.

The most important thing is, our server should have an API which accepts search keyword as a parameter.

We will use the Github issue search API as a server end point as explained in mat-table server side sort article.

We will search issues by their title using our mat-table search box.

https://api.github.com/search/issues?q=windows+in:title+repo:angular/components&sort=created&order=desc&page=1&per_page=50

To search github issues by title we need to pass search keyword in the below following format.

q=windows+in:title

Here “windows” is our search keyword.

You need to create your own Server API.

Step 3: Create a Service which calls the backend API

I have created a service called GithubService with the method called getRepoIssues().

Inside this method, using HttpClient.get() method we will call the API end point.


public getRepoIssues(
    filter: string,
    sort: string,
    order: SortDirection,
    page: number,
    perpage: number
  ): Observable<GithubApi> {
    const href = 'https://api.github.com/search/issues';
    const requestUrl = `${href}?q=${filter}+in:title+repo:angular/components&sort=${sort}&order=${order}&page=${page}&per_page=${perpage}`;

    return this.http.get<GithubApi>(requestUrl);
  }

In addition to the sorting & pagination parameters, I have added filter parameter to the getRepoIssues() issues.

And added two interfaces called GithubApi and GithubIssue.


export interface GithubApi {
  items: GithubIssue[];
  total_count: number;
}

export interface GithubIssue {
  created_at: string;
  number: string;
  state: string;
  title: string;
}

And inject the service in our mat-table component.


constructor(public githubService: GithubService) {}

Step 4: Subscribe to search formControl.valueChanges event

Whenever there is a change in search filter we need to go to the server add filter the records and display them in the mat-table.

In ngAfterViewInit() method, we will subscribe to search formControl.valueChanges event.

The formControl.valueChanges event trigger the getRepoIssues() method to get the latest filtered records.

As we need to add server side sorting and pagination, we will combine search formControl.valueChanges event along with sort.sortChange and paginator.page events.


ngAfterViewInit() {

    // If the user changes the sort order, reset back to the first page.
    this.sort.sortChange.subscribe(() => (this.paginator.pageIndex = 0));

    merge(this.searchKeywordFilter.valueChanges, this.sort.sortChange, this.paginator.page)
      .pipe(
        startWith({}),
        switchMap(() => {
          this.isLoadingResults = true;
          var filterValue = this.searchKeywordFilter.value == null ? '' : this.searchKeywordFilter.value;
          return this.githubService
            .getRepoIssues(
              filterValue,
              this.sort.active,
              this.sort.direction,
              this.paginator.pageIndex + 1,
              this.paginator.pageSize
            )
            .pipe(catchError(() => observableOf(null)));
        }),
        map((data) => {
          // Flip flag to show that loading has finished.
          this.isLoadingResults = false;
          this.isRateLimitReached = data === null;

          if (data === null) {
            return [];
          }

          // Only refresh the result length if there is new data. In case of rate
          // limit errors, we do not want to reset the paginator to zero, as that
          // would prevent users from re-triggering requests.
          this.resultsLength = data.total_count;
          return data.items;
        })
      )
      .subscribe((data) => (this.data = data));
  }

Iam using Rxjs merge operator to combine three events.

If the search filter value is null, we need to pass empty string other wise we need to pass the actual value.


var filterValue = this.searchKeywordFilter.value == null ? '' : this.searchKeywordFilter.value;

return this.githubService
            .getRepoIssues(
              filterValue,
              this.sort.active,
              this.sort.direction,
              this.paginator.pageIndex + 1,
              this.paginator.pageSize
            )

I am using series of Rxjs Operators like startWith, switchMap, map along merge operator.

In the switchMap operator I am returning getRepoIssues observable with search filter keyword along with sort column, sort direction, page size and page number parameters.

mat-paginator contains pageIndex and pageSize attributes which represents page number and page size of mat-table.

As pageIndex start with zero, I am adding +1 to the page index.

In the map operator I am assigning total number of filtered records to the resultsLength variable and returning github issues from the items property of GithubApi.

Finally in the subscribe method, assigning the filtered github issues data to mat-table data source.

When we type in the search box or if there is a change in mat-paginator or when we click on sorting column the above events will be triggered and the mat-table data will be updated accordingly.

Adding progress spinner to mat-table while filtering

While searching the records from the server we need to add a progress spinner to represent the status of the API call.

I am using isLoadingResults property to display or hide the progress spinner.


  <div
    class="example-loading-shade"
    *ngIf="isLoadingResults || isRateLimitReached"
  >
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
    <div class="example-rate-limit-reached" *ngIf="isRateLimitReached">
      GitHub's API rate limit has been reached. It will be reset in one minute.
    </div>
  </div>


mat-table server side search & filter StackBlitz Demo

Here is the demo for mat-table server side search & filter StackBlitz

https://angular-mat-table-server-side-search-filter-example.stackblitz.io

Avatar

Arunkumar Gudelli

Liked this post? Subscribe