Let's Make it More Interesting
We just created a very simple table, but how do me make a table in Angular Material that actually has the features we've come to depend on in jQuery DataTables? There is no one "right" answer, which is actually pretty great (a large part of Angular's power lies in its flexibility). That being said, let's start by mimicking the "default" functionality of jQuery DataTables with a simple "search all" at the top of the table and pagination at the bottom. (with a more diverse set of data). We're going to mainly use a combination of this example and this tutorial, however we are going to use our API data (unlike the first example), are going to modify the tutorial example to use a different means to get our data upon search (largely to show we can) and finally are going to use Angular 7 (the tutorial uses Angular 6, and the largest change is mapping our data return and making use of switchMap in the Observable we subscribe to, as in the example above).
First, let's add our search element following largely the tutorial's example, as the example from the Angular Material Documentation "works" without issue, but lacks a great deal of flexibility due to the fact that it embeds the keyup() function inside of the HTML:
<mat-form-field>
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
</mat-form-field>
The approach used in the tutorial is much more flexible and (as mentioned there) allows us to prevent overwhelming our (or another person's) API with a ton of requests on each user keyup/keydown event. It (and this example) first begins with a native input element:
<mat-form-field>
<input matInput placeholder="Search" #input>
</mat-form-field>
Then (after starting with blank content in that filter, shown in the complete component here we can move along to determining if a user "really wants" to search by including an RxJS debounceTime (which you can of course adjust depending on user experience and API rate) and RxJS distinctUntilChange (which prevents hits on the API when there is no change in the filter criteria):
fromEvent(this.input.nativeElement, 'keyup')
.pipe(
debounceTime(200),
distinctUntilChanged(),
Now whenever the content of the "Search" field changes, it will be passed to the employee service's getEmployees function as a parameter, and in turn passed along to the API:
getEmployees(sortDirection: string, sortParameter: string, filter: string, start: number, end: number): Observable<EmployeeApi> {
sortDirection = sortDirection.trim();
sortParameter = sortParameter.trim();
filter = filter.trim();
//toString is necessary here because httpClient does not support anything but strings
let httpParams = new HttpParams({
fromObject: {
sortDirection: sortDirection, sortParameter: sortParameter, filter: filter,
start: start.toString(), end: end.toString()
}
});
You may have noticed that function and service overall are not much different than the Departments Service and its getDepartmentsfunction. In fact, we've just added a few parameters. Those of us migrating from jQuery DataTables Server-Side processing might already spot an advantage: I've used a standard and conventional Controller in the API that is really not customized for this table at all. In fact, even if I did not have the ability tocustomize this API, it would be reasonable to expect to pass in a filter and get results. Server-side jQuery DataTables has rather strict requirements for the server-side script, thus making it difficult to implement if you lack access to an API, or want to reuse an endpoint for another application.
There is also a very solid implementation of pagination in that tutorial (with a flexible number of rows per page), and it's not tough at all to add it to our table and make it function largely like the familiar DataTables component. It depends on resultsLength, which is easily obtained from the "count" in the "API" interface. First, we'll add a "mat-paginator" element to the bottom of the table:
<mat-paginator [length]="resultsLength" [pageSize]="10" [pageSizeOptions]="[10, 20, 50]"></mat-paginator>
Then, back in the employee table component, we'll make use of the RxJS Merge function to emit the values of a change in the sort parameter and (now) the pagination element (note that we are also resetting pagination to page zero when sorting changes):
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
this.sort.direction = 'asc';
this.sort.active = 'emp_no';
this.paginator.pageIndex = 0;
this.paginator.pageSize = 10;
this.input.nativeElement.value = '';
this.sort.sortChange.subscribe();
this.employeeService = new EmployeeService(this.http);
merge(this.sort.sortChange, this.paginator.page)
.pipe(
startWith({}),
switchMap(() => {
this.isLoadingResults = true;
return this.employeeService!.getEmployees(
this.sort.direction,
this.sort.active,
this.input.nativeElement.value,
this.paginator.pageIndex,
this.paginator.pageSize)
}),
map(data => {
// Flip flag to show that loading has finished.
this.isLoadingResults = false;
this.resultsLength = data.total_count;
return data.items;
}),
).subscribe(data => this.data = data);
}
Now whenever the user changes the pagination, filter, or (like the first, very simple table) sort parameter, we make another request to the API for the data. This now looks, feels, and functions very much like a jQuery DataTable, as you'll notice:
(click here for the complete, live example).
If you take a look at the complete HTML of the table component, you'll note that I've added some other small "tweaks", such as disabling sorting on the two date fields (simply because sorting by date is painfully slow on my very small database server). These changes are very easy, and even fairly large visual and functional modifications aren't difficult, as you'll notice from the complete set of examples on the Angular Material Documentation page (which is frequently expanded) now that you have a functioning table.
Top comments (0)