Angular material sort

Angular material sort DEFAULT

We are going to divide this article into two major parts. The first part will consist of creating environment files, HTTP repository service, and creating a new Owner module with the lazy loading feature. As you can see, everything is Angular specific, so we won’t dive too deep into these sections. We already have Angular Series in which we have talked about these topics in great detail. So if you are not familiar with these topics, we strongly recommend reading the mentioned series.

In our source code, we can find the  folder which contains the entire .NET Core project, which we have created in .NET Core Series. In the same folder, we can find the folder which contains a script to create a MySQL database with its tables. Just run that script in the MySQL database and you are ready to go.

The second part will consist of creating a material table and populating that table with data from our server. Furthermore, we are going to create the filter, sorting, and paging functionalities for that table.

So, it’s time to start our job.

  • Getting started with Angular Material
  • Navigation Menu – Sidebar, Main Navigation
  • Angular Material Table, Filter, Sort, Paging (Current article)
  • Angular Material Progress Bar, Spinner, CheckBox, Card, Select, Expansion Panel
  • Material Inputs, DatePicker, Form Validation, Modals

For the complete navigation and all the basic instructions of the Angular Material series, check out: Introduction of the Angular Material series.

The source code is available at GitHub Angular Material Table – Source Code

We are going to divide this post into several sections:

Environment, HTTP and Owner Module

Let’s start with the environment file modifications.

We are going to modify the file first:

export const environment = { production: true, urlAddress: 'http://www.ang-material-account-owner.com' };
After that, let’s modify thefile:
export const environment = { production: false, urlAddress: 'http://localhost:5000' };
Having these environment files modified, it is time to create a service for sending the HTTP requests towards our server.

To do that, we are going to create a service file first:

ng g service shared/repository --skipTests
After creation, we have to modify that file:
import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { environment } from './../../environments/environment'; @Injectable({ providedIn: 'root' }) export class RepositoryService { constructor(private http: HttpClient) { } public getData = (route: string) => { return this.http.get(this.createCompleteRoute(route, environment.urlAddress)); } public create = (route: string, body) => { return this.http.post(this.createCompleteRoute(route, environment.urlAddress), body, this.generateHeaders()); } public update = (route: string, body) => { return this.http.put(this.createCompleteRoute(route, environment.urlAddress), body, this.generateHeaders()); } public delete = (route: string) => { return this.http.delete(this.createCompleteRoute(route, environment.urlAddress)); } private createCompleteRoute = (route: string, envAddress: string) => { return `${envAddress}/${route}`; } private generateHeaders = () => { return { headers: new HttpHeaders({'Content-Type': 'application/json'}) } } }
Excellent. We have prepared our service file. If you want to learn more about environment files, services, and HTTP, you can read that in the Angular Series Articlewhich covers all of these topics.

One more thing that we need to do is to register in the file:

import { HttpClientModule } from '@angular/common/http';
imports: [ … HttpClientModule ],

Creating a New Owner Module

Let’s create a new Owner module, and the routes for that module as well:

ng g module owner --module app
We are going to register this module into the main routing module but in such a way to support the lazy loading feature:
const routes: Routes = [ { path: 'home', component: HomeComponent}, { path: 'owner', loadChildren: () => import('./owner/owner.module').then(m => m.OwnerModule) }, { path: '', redirectTo: '/home', pathMatch: 'full' } ];
To read more about multiple modules and lazy loading in Angular, you can visit an article on the following link Lazy Loading in Angular.

Right now, we have to create a new component to show the list of all the owners from the database:

ng g component owner/owner-list --skipTests
owner list component - angular material table

We need to have routing for the components inside this module, so let’s create a new routing module for the Owner module components:

ng g module owner/owner-routing --module owner
And let’s modify that module file:
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Routes, RouterModule } from '@angular/router'; import { OwnerListComponent } from '../owner-list/owner-list.component'; const routes: Routes = [ { path: 'owners', component: OwnerListComponent } ]; @NgModule({ imports: [ CommonModule, RouterModule.forChild(routes) ], exports: [ RouterModule ], declarations: [] }) export class OwnerRoutingModule { }
Finally, to make all this to work, we need to modify our routes in the file:
<a mat-list-item routerLink="/owner/owners" (click)="onSidenavClose()"> <mat-icon>assignment_ind</mat-icon> <span class="nav-caption">Owner Actions</span> </a>
And the file:
<li> <a routerLink="/owner/owners">Owner Actions</a> </li>
That is it. We can confirm now that our routing settings work as it supposed to:

routing completed - angular material table

 

Excellent. Right now, we can dedicate our work to fetch some data from the database and show them in the material table component.

Using Material Table to Display Data

Because we have created another module in our Angular app, we need to import the file inside the file:

import { MaterialModule } from './../material/material.module';
imports: [ … MaterialModule ],
Once we create the Shared module, we will fix this code repetition (MaterialModule inside the App module and Owner module).

For now, let’s continue by creating the folder and inside it the file:

export interface Owner{ id: string; name: string; dateOfBirth: Date; address: string; }
Because we want to use the material table component, we need to register its own module in the file:
import { MatTableModule } from '@angular/material/table';
imports: [ MatTableModule,
exports: [ MatTableModule,
Then, let’s modify the file:
<table mat-table [dataSource]="dataSource"> <ng-container matColumnDef="name"> <th mat-header-cell *matHeaderCellDef> Name </th> <td mat-cell *matCellDef="let element"> {{element.name}} </td> </ng-container> <ng-container matColumnDef="dateOfBirth"> <th mat-header-cell *matHeaderCellDef> Date of Birth </th> <td mat-cell *matCellDef="let element"> {{element.dateOfBirth | date}} </td> </ng-container> <ng-container matColumnDef="address"> <th mat-header-cell *matHeaderCellDef> Address </th> <td mat-cell *matCellDef="let element"> {{element.address}} </td> </ng-container> <ng-container matColumnDef="details"> <th mat-header-cell *matHeaderCellDef> Details </th> <td mat-cell *matCellDef="let element"> <button mat-icon-button color="primary" (click)="redirectToDetails(element.id)"> <mat-icon class="mat-18">reorder</mat-icon> </button> </td> </ng-container> <ng-container matColumnDef="update"> <th mat-header-cell *matHeaderCellDef> Update </th> <td mat-cell *matCellDef="let element"> <button mat-icon-button color="accent" (click)="redirectToUpdate(element.id)"> <mat-icon class="mat-18">system_update</mat-icon> </button> </td> </ng-container> <ng-container matColumnDef="delete"> <th mat-header-cell *matHeaderCellDef> Delete </th> <td mat-cell *matCellDef="let element"> <button mat-icon-button color="warn" (click)="redirectToDelete(element.id)"> <mat-icon class="mat-18">delete</mat-icon> </button> </td> </ng-container> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> </table>
The element transforms this table into a material one. With the attribute, we provide a data source for our table. Inside every tag, we define the column definition and the value to be displayed. It is very important to match the value with the property name of our interface.

Finally, in the last two tags, we define an order for our header columns and the row definitions. So, what we need to do right now is to create our and properties in the file:

import { RepositoryService } from './../../shared/repository.service'; import { Component, OnInit } from '@angular/core'; import { MatTableDataSource } from '@angular/material/table'; import { Owner } from '../../_interface/owner.model'; @Component({ selector: 'app-owner-list', templateUrl: './owner-list.component.html', styleUrls: ['./owner-list.component.css'] }) export class OwnerListComponent implements OnInit { public displayedColumns = ['name', 'dateOfBirth', 'address', 'details', 'update', 'delete' ]; public dataSource = new MatTableDataSource<Owner>(); constructor(private repoService: RepositoryService) { } ngOnInit() { this.getAllOwners(); } public getAllOwners = () => { this.repoService.getData('api/owner') .subscribe(res => { this.dataSource.data = res as Owner[]; }) } public redirectToDetails = (id: string) => { } public redirectToUpdate = (id: string) => { } public redirectToDelete = (id: string) => { } }
If we change the order of elements inside the array, it will change the order of the columns inside our table.

Right now, if we start our application and navigate to the Owner Actions menu, we are going to see a populated material table. But we are missing some styles, so let’s add those in the file:

table { width: 100%; overflow-x: auto; overflow-y: hidden; min-width: 500px; } th.mat-header-cell { text-align: left; max-width: 300px; }
Now we should have a better-styled table:

mat table - angular material table

Sorting Data in Material Table

We want to add the sorting functionality to our table, and for that purpose, we are going to use the directive on the tag. Moreover, we need to place the directive for each header cell that will trigger sorting.

So, let’s do that now.

Modifying the tag is going to be our first task:

<table mat-table [dataSource]="dataSource" matSort>
Then, we are going to add the directive to the , , and tags:
<th mat-header-cell *matHeaderCellDef mat-sort-header> Name </th> ... <th mat-header-cell *matHeaderCellDef mat-sort-header> Date of Birth </th> ... <th mat-header-cell *matHeaderCellDef mat-sort-header> Address </th>
To make sorting functionality up and running, we need to modify the file as well:
import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core'; import { MatSort } from '@angular/material/sort'; ... export class OwnerListComponent implements OnInit, AfterViewInit { public displayedColumns = ['name', 'dateOfBirth', 'address', 'details', 'update', 'delete']; public dataSource = new MatTableDataSource<Owner>(); @ViewChild(MatSort) sort: MatSort; constructor(private repoService: RepositoryService) { } ngOnInit() { this.getAllOwners(); } ngAfterViewInit(): void { this.dataSource.sort = this.sort; } . . .
Lastly, we need to add the inside of the file:
import { MatSortModule } from '@angular/material/sort';
imports: [ MatSortModule,
exports: [ MatSortModule,
Now, we can check our result:

Sorting mat table

 

By default, sorting starts with ascending order first and then descending. We can change that behavior by adding the attribute to next to the directive:

<table mat-table [dataSource]="dataSource" matSort matSortStart="desc">
If we don’t want to use for sorting, but to provide our own sorting logic, we can use the event to receive the active sorting column and the sorting order as well:
<table mat-table [dataSource]="dataSource" matSort (matSortChange)="customSort($event)">
Once we click on the name column it will generate the following JSON object (Of course, don’t forget to add the function in the .ts file):
{active: "name", direction: "asc"} 1. active:"name" 2. direction:"asc" 3. __proto__:Object

Filter Functionality in Material Table

For this functionality, we need to provide our own input field and a custom function to filter our data. Only then, we can use ’s filter property. To implement filtering, we are going to add the following code right above our table in the HTML file:

<div fxLayout fxLayoutAlign="center center"> <mat-form-field fxFlex="40%"> <input matInput type="text" (keyup)="doFilter($event.target.value)" placeholder="Filter"> </mat-form-field> </div>
And then to write the following function in the component file:
public doFilter = (value: string) => { this.dataSource.filter = value.trim().toLocaleLowerCase(); }
Finally, because we are using the directive to transform regular input into the material input field, we need to register its modules inside the file:
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input';
imports: [ MatFormFieldModule, MatInputModule,
exports: [ MatFormFieldModule, MatInputModule,
As we can see from the HTML file, we are using the directive. But, because this component is part of a new Owner module, we need to import into the Owner module file as well:
import { FlexLayoutModule } from '@angular/flex-layout';
imports: [ ... FlexLayoutModule ],
Of course, this code repetition will be solved as well as soon as we create a Shared module.

Excellent.

Now we can inspect the result:

filter mat table - angular material table

 

Paging Functionality

To implement paging with a material table, we need to use a bellow our table. So, let’s start implementation by adding inside the module:

import {MatPaginatorModule } from '@angular/material/paginator';
imports: [ MatPaginatorModule,
exports: [ MatPaginatorModule,
Then, let’s add inside the HTML file:
<mat-paginator [pageSize]="2" [pageSizeOptions]="[2, 4, 6, 10, 20]"> </mat-paginator>
And finally, let’s modify the file:
import { MatPaginator } from '@angular/material/paginator'; ... @ViewChild(MatPaginator) paginator: MatPaginator; constructor(private repoService: RepositoryService) { } ngOnInit() { this.getAllOwners(); } ngAfterViewInit(): void { this.dataSource.sort = this.sort; this.dataSource.paginator = this.paginator; } ...
After these changes, we should have the following result:

paging - angular material table

If we want to write our custom pagination logic, we should use the output event:

<mat-paginator [pageSize]="2" [pageSizeOptions]="[2, 4, 6, 10, 20]" (page)="pageChanged($event)"> </mat-paginator>
For the custom pagination logic, you will have to write a pagination logic on the Web API part. We have a great article that explains How to Write Paging in ASP.NET Core Web API. So, feel free to read it and acquire a better knowledge of the server-side paging as well.

Conclusion

So, that’s it. Now we have our material table with all the features like sorting, paging, and filtering data.

In this article, we have learned:

  • How to use Environment files, HTTP client module, and Lazy Loading feature
  • To create a material table
  • To apply sorting, filtering, and pagination to the material table

In the next article, we are going to create error pages by focusing on the material components and to create owner details component.

Sours: https://code-maze.com/angular-material-table/

Angular Material 7 - Sort Header



The <mat-sort-header> and matSort, an Angular Directives, are used to add sorting capability to a table header.

In this chapter, we will showcase the configuration required to show a Sort Header using Angular Material.

Following is the content of the modified module descriptor app.module.ts.

import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import {MatSortModule} from '@angular/material' import {FormsModule, ReactiveFormsModule} from '@angular/forms'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, BrowserAnimationsModule, MatSortModule, FormsModule, ReactiveFormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }

Following is the content of the modified HTML host file app.component.html.

<table matSort (matSortChange) = "sortFood($event)"> <tr> <th mat-sort-header = "name">Dessert (100g)</th> <th mat-sort-header = "calories">Calories</th> <th mat-sort-header = "fat">Fat (g)</th> <th mat-sort-header = "carbs">Carbs (g)</th> <th mat-sort-header = "protein">Protein (g)</th> </tr> <tr *ngFor = "let food of sortedFood"> <td>{{food.name}}</td> <td>{{food.calories}}</td> <td>{{food.fat}}</td> <td>{{food.carbs}}</td> <td>{{food.protein}}</td> </tr> </table>

Following is the content of the modified ts file app.component.ts.

import {Component, Injectable} from '@angular/core'; import {Sort} from '@angular/material'; export interface Food { calories: number; carbs: number; fat: number; name: string; protein: number; } @Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrls: ['app.component.css'] }) export class AppComponent { foods: Food[] = [ {name: 'Yogurt', calories: 159, fat: 6, carbs: 24, protein: 4}, {name: 'Sandwich', calories: 237, fat: 9, carbs: 37, protein: 4}, {name: 'Eclairs', calories: 262, fat: 16, carbs: 24, protein: 6}, {name: 'Cupcakes', calories: 305, fat: 4, carbs: 67, protein: 4}, {name: 'Gingerbreads', calories: 356, fat: 16, carbs: 49, protein: 4}, ]; sortedFood: Food[]; constructor() { this.sortedFood = this.foods.slice(); } sortFood(sort: Sort) { const data = this.foods.slice(); if (!sort.active || sort.direction === '') { this.sortedFood = data; return; } this.sortedFood = data.sort((a, b) => { const isAsc = sort.direction === 'asc'; switch (sort.active) { case 'name': return compare(a.name, b.name, isAsc); case 'calories': return compare(a.calories, b.calories, isAsc); case 'fat': return compare(a.fat, b.fat, isAsc); case 'carbs': return compare(a.carbs, b.carbs, isAsc); case 'protein': return compare(a.protein, b.protein, isAsc); default: return 0; } }); } } function compare(a: number | string, b: number | string, isAsc: boolean) { return (a < b ? -1 : 1) * (isAsc ? 1 : -1); }

Result

Verify the result.

Sort Header

Details

  • Here, we've created a table. Added matSort and handles its matSortChange event.
Sours: https://www.tutorialspoint.com/angular_material7/angular_material7_sort_header.htm
  1. Peterbilt 379 horns
  2. Weiler forestry equipment
  3. Grey rock method covert narcissist

The and are used, respectively, to add sorting state and display to tabular data.

Adding sort to table headers

To add sorting behavior and styling to a set of table headers, add the component to each header and provide an that will identify it. These headers should be contained within a parent element with the directive, which will emit a event when the user triggers sorting on the header.

Users can trigger the sort header through a mouse click or keyboard action. When this happens, the will emit a event that contains the ID of the header triggered and the direction to sort ( or ).

Changing the sort order

By default, a sort header starts its sorting at and then . Triggering the sort header after will remove sorting.

To reverse the sort order for all headers, set the to on the directive. To reverse the order only for a specific header, set the input only on the header instead.

To prevent the user from clearing the sort state from an already sorted column, set to on the to affect all headers, or set to on a specific header.

Disabling sorting

If you want to prevent the user from changing the sorting order of any column, you can use the binding on the , or the on a single .

Using sort with the mat-table

When used on a header, it is not required to set a id on because by default it will use the id of the column.

Accessibility

When you apply to a header cell element, the component wraps the content of the header cell inside a button. The text content of the header cell then becomes the accessible label for the sort button. However, the header cell text typically describes the column and does not indicate that interacting with the control performs a sorting action. To clearly communicate that the header performs sorting, always use the input to provide a description for the button element, such as "Sort by last name".

applies the attribute to communicate the active sort state to assistive technology. However, most screen readers do not announce changes to the value of , meaning that screen reader users do not receive feedback that sorting occured. To remedy this, use the event on the directive to announce state updates with the service from .

If your application contains many tables and sort headers, consider creating a custom directives to consistently apply and announce sort state changes.

Sours: https://github.com/angular/components/blob/master/src/material/sort/sort.md
Angular 8 #11; Sorting in Angular Material Table

Angular Material Sort Header

next →← prev

The <mat-sort-header> is used to add sorting state and display the data in tabular data.

Adding sort to table headers

Adding sorting behavior and styling a set of table headers adds the <mat-sort-header> component to every heading and provides an ID that will recognize it. These headers can be included with the parent element along with the parent element directive, which triggers a truncation on any user header when a matSortChange event will emit.

Users can trigger sort headers by a single mouse click or a single keyboard action. When this happens, matSort will emit a matSortChange event having the (ID ascending or descending order) direction for sorting the ID and mat of the header.

Changing the sort order

By default, the sort header starts sorting on asc and then desc.

Set matSortStart to descending on the matSort directive to reverse the sort order of all headers. Instead of changing the order for the particular header, set the start input to the header only.

Disabling sorting

If we want to prevent the user from changing the order of any column, then we have to use the matSortDizable bindings when disabled on mat-sort or on the mat-sort-header.

Accessibility

The aria-label for the sort button will be set in MatSortHeaderIntl.

Example 1:

app.component.html

app.module.ts

app.component.css

Output:

Angular Material Sort Header

Using sort with the mat-table

It is not required to set a mat-sort-header id because it will use the column's id.

app.component.html

app.component.ts

app.component.css

Output:

Angular Material Sort Header

Next TopicAngular Material Divider



← prevnext →



Sours: https://www.javatpoint.com/angular-material-sort-header

Sort angular material

In this post, we are going to go through a complete example of how to use the Angular Material Data Table.

We are going to cover many of the most common use cases that revolve around the Angular Material Data Table component, such as: server-side pagination, sorting, and filtering.

This is a step-by-step tutorial, so I invite you to code along as we are going to start with a simple initial scenario. We will then progressively add features one by one and explain everything along the way (including gotchas).

We will learn in detail all about the reactive design principles involved in the design of the Angular Material Data Table and an Angular CDK Data Source.

The end result of this post will be:

  • a complete example of how to implement an Angular Material Data Table with server-side pagination, sorting and filtering using a custom CDK Data Source
  • a running example available on Github, which includes a small backend Express server that serves the paginated data

Table Of Contents

In this post, we will cover the following topics:

  • The Angular Material Data Table - not only for Material Design
  • The Material Data Table Reactive Design
  • The Material Paginator and Server-side Pagination
  • Sortable Headers and Server-side Sorting
  • Server-side Filtering with Material Input Box
  • A Loading Indicator
  • A Custom Angular Material CDK Data Source
  • Source Code (on Github) with the complete example
  • Conclusions

So without further ado, let's get started with our Material Data Table Guided Tour!

Importing Angular Material modules

In order to run our example, let's first import all the Angular Material modules that we will need:

Here is a breakdown of the contents of each Material module:

  • MatInputModule: this contains the components and directives for adding Material design Input Boxes to our application (needed for the search input box)
  • MatTableModule: this is the core data table module, which includes the component and many related components and directives
  • MatPaginatorModule: this is a generic pagination module, that can be used to paginate data in general. This module can also be used separately from the Data table, for example for implementing Detail pagination logic in a Master-Detail setup
  • MatSortModule: this is an optional module that allows adding sortable headers to a data table
  • MatProgressSpinnerModule: this module includes the progress indicator component that we will be using to indicate that data is being loaded from the backend

Introduction to the Angular Material Data Table

The Material Data Table component is a generic component for displaying tabulated data. Although we can easily give it a Material Design look and feel, this is actually not mandatory.

In fact, we can give the Angular Material Data table an alternative UI design if needed. To see that this is so, let's start by creating a Data Table where the table cells are just plain divs with no custom CSS applied.

This data table will display a list of course lessons, and has 3 columns (sequence number, description and duration):

Material Data Table Column Definitions

As we can see, this table defines 3 columns, each inside its own element. The element will NOT be rendered to the screen (see this post for more details), but it will provide an element for applying the directive.

The directive uniquely identifies a given column with a key: seqNo, description or duration. Inside the element, we will have all the configuration for a given column.

Notice that the order of the elements does NOT determine the column visual order

The Material Data Table Auxiliary Definition Directives

The Material Data Table has a series of auxiliary structural directives (applied using the syntax) that allow us to mark certain template sections has having a certain role in the overall data table design.

These directives always end with the postfix, and they are used to assign a role to a template section. The first two directives that we will cover are and .

The and Directives

Inside of each with a given column definition, there are a couple of configuration elements:

  • we have the template that defines how to display the header of a given column, identified via the structural directive
  • we also have another template that defines how to display the data cells of a given column, using the structural directive

These two structural directives only identify which template elements have a given role (cell template, header template), but they do not attach any styling to those elements.

For example, in this case, and are being applied to plain divs with no styling, so this is why this table does not have a Material design yet.

Applying a Material Design to the Data Table

Let's now see what it would take to give this Data Table a Material Look and Feel. For that, we will use a couple of built-in components in our header and cell template definitions:

This template is almost the same as the one we saw before, but now we are using the and components inside our column definition instead of plain divs.

Using these components, lets now have a look at what the Data Table looks like with this new Material Design:

Angular Material Data Table

Notice that the table already has some data! We will get to the data source in a moment, right now let's continue exploring the rest of the template.

The Directive

The data cell template has access to the data that is being displayed. In this case, our data table is displaying a list of lessons, so the lesson object in each row is accessible via the syntax, and can be used in the template just like any component variable.

The component and the directive

This combination of related component / directive works in the following way:

  • the identifies a configuration element for the table header row, but it does not apply any styling to the element
  • The on the other hand applies some minimal Material stying

The directive also defines in which order the columns should be displayed. In our case, the directive expression is pointing to a component variable named .

Here is what the component variable will look like:

The values of this array are the column keys, which need to be identical to the names of the column sections (specified via the directive).

Note: It's this array that determines the visual order of the columns!

The component and the directive

This component / directive pair also works in a similar way to what we have seen in previous cases:

  • identifies which element inside provides configuration for how a data row should look like, without providing any specific styling
  • on the other hand, will provide some Material stying to the data row

With , we also have a variable exported that we have named , containing the data of a given data row, and we have to specify the property, which contains the order on which the data cells should be defined.

Interacting with a given table data row

We can even use the element identified by the directive to interact with a given data row. For example, this is how we can detect if a given data row was clicked:

When a row is clicked, we will call the component method, that will then log the row data to the console:

If we now click on the first row of our data table, here is what the result will look like on the console:

Material Data Table

As we can see the data for the first row is being printed to the console, as expected! But where is this data coming from?

To answer that, let's then talk about the data source that is linked to this data table, and go over the Material Data Table reactive design.

Data Sources and the Data Table Reactive Design

The data table that we have been presenting receives the data that it displays from a Data Source that implements an Observable-based API and follows common reactive design principles.

This means for example that the data table component does not know where the data is coming from. The data could be coming for example from the backend, or from a client-side cache, but that is transparent to the Data table.

The Data table simply subscribes to an Observable provided by the Data Source. When that Observable emits a new value, it will contain a list of lessons that then get's displayed in the data table.

Data Table core design principles

With this Observable-based API, not only the Data table does not know where the data is coming from, but the data table also does not know what triggered the arrival of new data.

Here are some possible causes for the emission of new data:

  • the data table is initially displayed
  • the user clicks on a paginator button
  • the user sorts the data by clicking on a sortable header
  • the user types a search using an input box

Again, the Data Table has no information about exactly which event caused new data to arrive, which allows the Data Table components and directives to focus only on displaying the data, and not fetching it.

Let's then see how can we implement such a reactive data source.

Why not use ?

In this example, we will not be using the built-in because its designed for filtering, sorting and pagination of a client-side data array.

In our case, all the filtering, sorting and pagination will be happening on the server, so we will be building our own Angular CDK reactive data source from first principles.

Fetching Data from the backend

In order to fetch data from the backend, our custom Data Source is going to be using the . This is a standard Observable-based stateless singleton service that is built internally using the Angular HTTP Client.

Let's have a look at this service, and break down how its implemented:

Breaking down the implementation

As we can see, this service is completely stateless, and every method forwards calls to the backend using the HTTP client, and returns an Observable to the caller.

Our REST API is available in URLs under the directory, and multiple services are available (here is the complete implementation).

In this snippet, we are just showing the method, that allows to obtain one filtered and sorted page of lessons data for a given course.

Here are the arguments that we can pass to this function:

  • courseId: This identifies a given course, for which we want to retrieve a page of lessons
  • filter: This is a search string that will help us filter the results. If we pass the empty string '' it means that no filtering is done on the server
  • sortOrder: our backend allows us to sort based on the column, and with this parameter, we can specify is the sort order is ascending (which is the default value), or descending by passing the value
  • pageNumber: With the results filtered and sorted, we are going to specify which page of that full list of results we need. The default is to return the first page (with index 0)
  • pageSize: this specifies the page size, which defaults to a maximum of 3 elements

With this arguments, the method will then build an HTTP GET call to the backend endpoint available at .

Here is what an HTTP GET call that fetches the lessons for the first page looks like:

As we can see, we are appending a series of HTTP query parameters to the GET URL using the fluent API.

This method will be the basis of our Data Source, as it will allow us to cover the server pagination, sorting and filtering use cases.

Implementing a Custom Angular CDK Data Source

Using the , let's now implement a custom Observable-based Angular CDK Data Source. Here is some initial code, so that we can discuss its Reactive design (the full version is shown in a moment):

Breaking down the design of an Angular CDK Data Source

Has we can see, in order to create a Data Source we need to create a class that implements . This means that this class needs to implement a couple of methods: and .

Note that these methods provide an argument which is a , which provides an Observable that emits information about what data is being displayed (the start index and the end index).

We would recommend for now not to focus so much on the at this moment, but on something much more important for understanding the whole design: the return value of the method.

How to implement the DataSource method

This method will be called once by the Data Table at table bootstrap time. The Data Table expects this method to return an Observable, and the values of that observable contain the data that the Data Table needs to display.

In this case, this observable will emit a list of Lessons. As the user clicks on the paginator and changes to a new page, this observable will emit a new value with the new lessons page.

We will implement this method by using a subject that is going to be invisible outside this class. That subject (the ) is going to be emitting the values retrieved from the backend.

The is a , which means its subscribers will always get its latest emitted value (or an initial value), even if they subscribed late (after the value was emitted).

Why use ?

Using is a great way of writing code that works independently of the order that we use to perform asynchronous operations such as: calling the backend, binding the data table to the data source, etc.

For example, in this design, the Data Source is not aware of the data table or at which moment the Data Table will require the data. Because the data table subscribed to the observable, it will eventually get the data, even if:

  • the data is still in transit coming from the HTTP backend
  • or if the data was already loaded

Custom Material CDK Data Source - Full Implementation Review

Now that we understand the reactive design of the data source, let's have a look at the complete final implementation and review it step-by-step.

Notice that in this final implementation, we have also included the notion of a loading flag, that we will use to display a spinning loading indicator to the user later on:

Data Source Loading Indicator Implementation Breakdown

Let's start breaking down this code, we will start first with the implementation of the loading indicator. Because this Data Source class has a reactive design, let's implement the loading flag by exposing a boolean observable called .

This observable will emit as first value (which is defined in the constructor), meaning that no data is loading initially.

The observable is derived using from a subject that is kept private to the data source class. The idea is that only this class knows when data is loading, so only this class can access the subject and emit new values for the loading flag.

The method implementation

Let's now focus on the implementation of the connect method:

This method will need to return an Observable that emits the lessons data, but we don't want to expose the internal subject directly.

Exposing the subject would mean yielding control of when and what data gets emitted by the data source, and we want to avoid that. We want to ensure that only this class can emit values for the lessons data.

So we are also going to return an Observable derived from using the method. This gives the data table (or any other subscriber) the ability to subscribe to the lessons data observable, without being able to emit values for that same observable.

The method implementation

Let's now break down the implementation of the disconnect method:

This method is called once by the data table at component destruction time. In this method, we are going to complete any observables that we have created internally in this class, in order to avoid memory leaks.

We are going to complete both the and the , which are then going to trigger the completion of any derived observables.

The method implementation

Finally, let's now focus on the implementation of the loadLessons method:

The Data Source exposes this public method named . This method is going to be called in response to multiple user actions (pagination, sorting, filtering) to load a given data page.

Here is how this method works:

  • the first thing that we will do is to report that some data is being loaded, by emitting to the , which will cause to also emit true
  • the is going to be used to get a data page from the REST backend
  • a call to is made, that returns an Observable
  • by subscribing to that observable, we trigger an HTTP request
  • if the data arrives successfully from the backend, we are going to emit it back to the data table, via the Observable
  • for that, we will call on the with the lessons data
  • the derived lessons observable returned by will then emit the lessons data to the data table

Handling Backend Errors

Let's now see, still in the method, how the Data Source handles backend errors, and how the loading indicator is managed:

  • if an error in the HTTP request occurs, the Observable returned by will error out
  • If that occurs, we are going to catch that error using and we are going to return an Observable that emits the empty array using
  • we could complementary also use another to show a closable error popup to the user
  • wether the call to the backend succeeds or fails, we will in both cases have the Observable emit by using (which works like in plain Javascript try/catch/finally)

And with this last bit, we have completed the review of our custom Data Source!

This version of the data source will support all our use cases: pagination, sorting and filtering. As we can see, the design is all about providing data transparently to the Data Table using an Observable-based API.

Let's now see how we can take this Data Source and plug it into the Data Table.

Linking a Data Source with the Data Table

The Data Table will be displayed as part of the template of a component. Let's write an initial version of that component, that displays the first page of lessons:

This component contains a couple of properties:

  • the array defines the visual order of the columns
  • The property defines an instance of , and that is being passed to via the template

Breaking down the method

In the method, we are calling the Data Source method to trigger the loading of the first lessons page. Let's detail what happens as a result of that call:

  • The Data Source calls the , which triggers an HTTP request to fetch the data
  • The Data Source then emits the data via the , which causes the Observable returned by to emit the lessons page
  • The Data Table component has subscribed to the observable and retrieves the new lessons page
  • The Data Table then displays the new lessons page, without knowing where the data came from or what triggered its arrival

And with this "glue" component in place, we now have a working Data Table that displays server data!

The problem is that this initial example is always loading only the first page of data, with a page size of 3 and with no search criteria.

Let's use this example as a starting point, and starting adding: a loading indicator, pagination, sorting, and filtering.

Displaying a Material Loading Indicator

In order to display the loading indicator, we are going to be using the observable of the Data Source. We will be using the Material component:

As we can see, we are using the pipe and to show or hide the material loading indicator. Here is what the table looks like while the data is loading:

Material Data Table Loading Indicator

We will also be using the loading indicator when transitioning between two data pages using pagination, sorting or filtering.

Adding a Data Table Material Paginator

The Material Paginator component that we will be using is a generic paginator that comes with an Observable-based API. This paginator could be used to paginate anything, and it's not specifically linked to the Data Table.

For example, on a Master-Detail component setup, we could use this paginator to navigate between two detail elements.

This is how the component can be used in a template:

As we can see, there is nothing in the template linking the paginator with either the Data Source or the Data Table - that connection will be done at the level of the .

The paginator only needs to know how many total items are being paginated (via the length property), in order to know how many total pages there are!

Its based on that information (plus the current page index) that the paginator will enable or disable the navigation buttons.

In order to pass that information to the paginator, we are using the property of a new object.

How to Link the Material Paginator to the Data Source

Let's now have a look at the , to see where is coming from and how the paginator is linked to the Data Source:

Breaking down the method

Let's start with the object: as we can see this object is available at component construction time via the router.

This data object was retrieved from the backend at router navigation time using a router Data Resolver (see an example here).

This is a very common design, that ensures that the target navigation screen already has some pre-fetched data ready to display.

We are also loading the first page of data directly in this method (on line 20).

How is the Paginator linked to the Data Source?

We can see in the code above that the link between the paginator and the Data Source is done in the method, so let's break it down:

We are using the lifecycle hook because we need to make sure that the paginator component queried via is already available.

The paginator also has an Observable-based API, and it exposes a Observable. This observable will emit a new value every time that the user clicks on the paginator navigation buttons or the page size dropdown.

So in order to load new pages in response to a pagination event, all we have to do is to subscribe to this observable, and in response to a pagination event, we are going to make a call to the Data Source method, by calling .

In that call to , we are going to pass to the Data Source what page index we would like to load, and what page size, and that information is taken directly from the paginator.

Why have we used the operator?

We could also have done the call to the data source from inside a handler, but in this case, we have implemented that call using the pipeable version of the RxJs operator called .

View the Paginator in Action

And with this in place, we now have a working Material Paginator! Here is what the Material Paginator looks like on the screen, while displaying page 2 of the lessons list:

Angular Material Paginator

Let's now continue to add more features to our example, let's add another very commonly needed feature: sortable table headers.

In order to add sortable headers to our Data Table, we will need to annotate it with the directive. In this case, we will make only one column in the table sortable, the column.

Here is what the template with all the multiple sort-related directives looks like:

Besides the directive, we are also adding a couple of extra auxiliary sort-related directives to the component:

  • matSortActive: When the data is passed to the Data Table, its usually already sorted. This directive allows us to inform the Data Table that the data is already initally sorted by the column, so the column sorting icon will be displayed as an upwards arrow
  • matSortDirection: This is a companion directive to , it specifies the direction of the initial sort. In this case, the data is initially sorted by the column in ascending order, and so the column header will adapt the sorting icon accordingly (screenshot below)
  • matSortDisableClear: Sometimes, besides ascending and descending order we might want a third "unsorted" state for the sortable column header, where we can clear the sorting order. In this case, we want to disable that to make sure the column always shown either the ascending or descending states

This is the sort configuration for the whole data table, but we also need to identify exactly what table headers are sortable!

In our case, only the column is sortable, so we are annotating the column header cell with the directive.

And this covers the template changes, let's now have a look at the changes we made to the in order to enable table header sorting.

Linking the Sortable column header to the Data Source

Just like the case of pagination, the sortable header will expose an Observable that emits values whenever the user clicks on the sortable column header.

The directive then exposes a sort Observable, that can trigger a new page load in the following way:

As we can see, the sort Observable is now being merged with the page observable! Now a new page load will be triggered in two cases:

  • when a pagination event occurs
  • when a sort event occurs

The sort direction of the column is now taken from the sort directive (injected via ) to the backend.

Notice that after each sort we are also resetting the paginator, by forcing the first page of the sorted data to be displayed.

Here is what the Data Table with sortable headers looks like, after loading the data and clicking the sortable header (triggering a descending sort by ):

Material Data Table with Sortable Header

Notice the sort icon on the column

At this point, we have server pagination and sorting in place. We are now ready to add the final major feature: server-side filtering.

In order to implement server-side filtering, the first thing that we need to do is to add a search box to our template.

And because this is the final version, let's then display the complete template with all its features: pagination, sorting and also server-side filtering:

Breaking down the Search Box implementation

As we can see, the only new part in this final template version is the , containing the Material Input box where the user types the search query.

This input box follows a common pattern found in the Material library: The is wrapping a plain HTML input and projecting it.

This gives us full access to all standard input properties including for example all the Accessibility-related properties. This also gives us compatibility with Angular Forms, as we can apply Form directives directly in the input HTML element.

Read more about how to build a similar component in this post: Angular ng-content and Content Projection: The Complete Guide.

Notice that there is not even an event handler attached to this input box ! Let's then have a look at the component and see how this works.

Final Component with Server Pagination, Sorting and Filtering

This is the final version of with all features included:

Let's then focus on breaking down the server filtering part.

Getting a reference to the Search Input Box

We can see that we have injected a DOM reference to the element using . Notice that this time around, the injection mechanism gave us a reference to a DOM element and not to a component.

With that DOM reference, here is the part that triggers a server-side search when the user types in a new query:

What we are doing in this is snippet is: we are taking the search input box and we are creating an Observable using .

This Observable will emit a value every time that a new keyUp event occurs. To this Observable we will then apply a couple of operators:

  • : The user can type quite quickly in the input box, and that could trigger a lot of server requests. With this operator, we are limiting the amount of server requests emitted to a maximum of one every 150ms.

  • : This operator will eliminate duplicate values

And with these two operators in place, we can now trigger a page load by passing the query string, the page size and page index to the the Data Source via the operator.

Let's now have a look at the what the screen would look like if the user types the search term "hello":

Angular Material Server Filtering

And with this in place, we have completed our example! We now have a complete solution for how to implement an Angular Material Data Table with server-side pagination, sorting and filtering.

Let's now quickly summarize what we have learned.

Conclusions

The Data Table, the Data Source and related components are a good example of a reactive design that uses an Observable-based API. Let's highlight the key points of the design:

  • the Material Data Table expects to receive the data from the Data Source via an Observable
  • The Data Source main role is to build and provide an Observable that emits new versions of the tabular data to the Data Table
  • A component class like will then "glue" everything together

This reactive design helps to ensure the loose coupling of the multiple elements involved, and provides a strong separation of concerns.

Source Code + Github Running Example

A running example of the complete code is available here on this branch on Github, and it includes a small backend Express server that serves the data and does the server-side sorting/pagination/filtering.

I hope that this post helps with getting started with the Angular Material Data Table and that you enjoyed it!

If you would like to learn a lot more about Angular Material, we recommend checking the Angular Material Course, where we cover different types of widgets in much more detail.

If you have some questions or comments please let me know in the comments below and I will get back to you.

To get notified of upcoming posts on Angular Material and other Angular topics, I invite you to subscribe to our newsletter:

If you are just getting started learning Angular, have a look at the Angular for Beginners Course:

Other Angular Material posts:

Angular Material Dialog: A Complete Example

Sours: https://blog.angular-university.io/angular-material-data-table/
Add Sorting In Covid 19 Application Using Angular Material - Angular Tutorial - Coding Knowledge

.

You will also be interested:

.



218 219 220 221 222