Using the Angular Material Paginator With ASP.NET Core and Angular
In this post, I want to show you how to use Angular Material with Angular to use a table with paging which is driven by an ASP.NET Core WebAPI.
Join the DZone community and get the full member experience.
Join For FreeCode
You can find the code here: https://github.com/FabianGosebrink/ASPNETCore-Angular-Material-HATEOAS-Paging
Get Started
With the Angular Material Table and its Pagination Module, it is quite easy to set up paging in a beautiful way so that you can use it on the client-side and only show a specific amount of entries to your users. What we do not want to do is load all the items from the backend in the first place just to get the paging going and then display only a specific amount. Instead, we want to load only what we need and display that. If the user clicks on the “next page” button, the items should be loaded and displayed.
The Backend
The backend is an ASP.NET Core WebAPI which sends out the data as JSON. With it, every entry contains the specific links and also all links containing the paging links to the next page, previous page, etc., although we do not need them in this example because we already have some implemented logic from Angular Material. If you would not use Angular Material or another “intelligent” UI piece giving you a paging logic, you could use the links to make it all by yourself.
Customer Controller
We are sending back the information about the paging with HATEOAS but also with a header to read it with Angular later. The totalcount
is especially interesting for the client. You could also send this back with the JSON response.
If you do send it back via the header, be sure to expand the headers in CORS that they can be read on the client-side.
There is also a parameter which can be passed to the GetAll
method: QueryParameters
.
The modelbinder
from ASP.NET Core can map the parameters in the request to this object and you can start using them as follows: http://localhost:5000/api/customers?pagecount=10&page=1&orderby=Name
is a valid request then which gives us the possibility to grab only the range of items we want to.
Front-End
The front-end is built with Angular and Angular Material. We'll get into the details below.
PaginationService
This service is used to collect all the information about the pagination. We are injecting the PaginationService
and consuming its values to create the URL and send the request.
We are exposing three properties here which can be changed through the “change()
” method. The method takes a pageEvent
as a parameter which comes from the Angular Material Paginator. There, all the information about the current paging state is stored. We are passing this thing around to get the information about our state of paging, which has kind of an abstraction of the pageEvent
from Angular Material.
HttpBaseService
We are injecting the PaginationService
and consuming its values to create the URL to which we are sending the request.
The Components
Beside the services, the components consume those services and the values. They are reacting on the pageswitch
event and are separated into stateful and stateless components.
Include in Module
In the ListComponent
we are now using the paginator module, but, first, we have to include it in our module like this:
Now we can use it in our view like this:
ListComponent
<div class="example-container mat-elevation-z8">
<mat-table #table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="id">
<mat-header-cell *matHeaderCellDef mat-sort-header> No. </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.id}} </mat-cell>
</ng-container>
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef mat-sort-header> Name </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.name}} </mat-cell>
</ng-container>
<ng-container matColumnDef="created">
<mat-header-cell *matHeaderCellDef mat-sort-header> Created </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.created | date}} </mat-cell>
</ng-container>
<ng-container matColumnDef="actions">
<mat-header-cell *matHeaderCellDef mat-sort-header> Actions </mat-header-cell>
<mat-cell *matCellDef="let element">
<button mat-icon-button (click)="onDeleteCustomer.emit(element)"><mat-icon>delete</mat-icon></button>
<a mat-icon-button [routerLink]="['/details', element.id]"><mat-icon>edit</mat-icon></a>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
</div>
<mat-paginator [length]="totalCount"
[pageSize]="paginationService.pageSize"
[pageSizeOptions]="paginationService.selectItemsPerPage"
(page)="onPageSwitch.emit($event)">
</mat-paginator>
The pageSize
and pageSizeOptions
come from the PaginationService
which we injected in the underlying component. On the (page)
event, we are firing the eventemitter
and calling the action which is bound to it in the stateful component.
As the ListComponent
is a stateless service, it gets passed all the values it needs when using it on the stateful component, OverviewComponent
OverviewComponent
<app-list
[dataSource]="dataSource"
[totalCount]="totalCount"
(onDeleteCustomer)="delete($event)"
(onPageSwitch)="switchPage($event)"
></app-list>
The switchPage
method is called when the page changes and first sets all the new values in the paginationService
and then gets the customers again. Those values are then provided again in the DataService
, and are consumed there, and also used in the view where they get displayed correctly.
In the getAllCustomers
method, we are reading the totalCount
value from the headers. Be sure to read the full response in the DataService
by adding return this.httpClient.get<T>(mergedUrl, { observe: 'response' });
and exposing the header in the CORS options like we did earlier.
Thanks for reading!
Fabian
Links
Published at DZone with permission of Fabian Gosebrink, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments