Angular Material 2 server pagination table

I am trying to achieve Angular Material 2, server side paging of MatPaginator. How can I achieve this?

The following is sample code:

<div class="example-container mat-elevation-z8"> <mat-table #table [dataSource]="dataSource"> <!-- Position Column --> <ng-container matColumnDef="position"> <mat-header-cell *matHeaderCellDef> No. </mat-header-cell> <mat-cell *matCellDef="let element"> {{element.position}} </mat-cell> </ng-container> <!-- Name Column --> <ng-container matColumnDef="name"> <mat-header-cell *matHeaderCellDef> Name </mat-header-cell> <mat-cell *matCellDef="let element"> {{element.name}} </mat-cell> </ng-container> <!-- Weight Column --> <ng-container matColumnDef="weight"> <mat-header-cell *matHeaderCellDef> Weight </mat-header-cell> <mat-cell *matCellDef="let element"> {{element.weight}} </mat-cell> </ng-container> <!-- Symbol Column --> <ng-container matColumnDef="symbol"> <mat-header-cell *matHeaderCellDef> Symbol </mat-header-cell> <mat-cell *matCellDef="let element"> {{element.symbol}} </mat-cell> </ng-container> <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row> </mat-table> <mat-paginator #paginator [pageSize]="10" [pageSizeOptions]="[5, 10, 20]"> </mat-paginator> </div> 

Pagination Component:

 import {Component, ViewChild} from '@angular/core'; import {MatPaginator, MatTableDataSource} from '@angular/material'; /** * @title Table with pagination */ @Component({ selector: 'table-pagination-example', styleUrls: ['table-pagination-example.css'], templateUrl: 'table-pagination-example.html', }) export class TablePaginationExample { displayedColumns = ['position', 'name', 'weight', 'symbol']; dataSource = new MatTableDataSource<Element>(ELEMENT_DATA); @ViewChild(MatPaginator) paginator: MatPaginator; /** * Set the paginator after the view init since this component will * be able to query its view for the initialized paginator. */ ngAfterViewInit() { this.dataSource.paginator = this.paginator; } } export interface Element { name: string; position: number; weight: number; symbol: string; } const ELEMENT_DATA: Element[] = [ {position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'}, {position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'}, {position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'}, {position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'}, {position: 5, name: 'Boron', weight: 10.811, symbol: 'B'}, {position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'}, {position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N'}, {position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O'}, {position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F'}, {position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne'}, {position: 11, name: 'Sodium', weight: 22.9897, symbol: 'Na'}, {position: 12, name: 'Magnesium', weight: 24.305, symbol: 'Mg'}, {position: 13, name: 'Aluminum', weight: 26.9815, symbol: 'Al'}, {position: 14, name: 'Silicon', weight: 28.0855, symbol: 'Si'}, {position: 15, name: 'Phosphorus', weight: 30.9738, symbol: 'P'}, {position: 16, name: 'Sulfur', weight: 32.065, symbol: 'S'}, {position: 17, name: 'Chlorine', weight: 35.453, symbol: 'Cl'}, {position: 18, name: 'Argon', weight: 39.948, symbol: 'Ar'}, {position: 19, name: 'Potassium', weight: 39.0983, symbol: 'K'}, {position: 20, name: 'Calcium', weight: 40.078, symbol: 'Ca'}, ]; 

How can I achieve pagination on the server side, which will be triggered when the next page click event changes or the page size changes to get the next set of records.

https://stackblitz.com/angular/qxxpqbqolyb?file=app%2Ftable-pagination-example.ts

+13
source share
4 answers

Based on Wilfredo's answer ( fooobar.com/questions/1274157 / ... ), I made a complete working example, as some questions were also missing from the question. Here is a more general case of server-side pagination and sorting using Angular 5 and Material Design (still need to enable filtering) - I hope this is useful to someone:

Paging Component:

 import { ViewChild, Component, Inject, OnInit, AfterViewInit } from '@angular/core'; import { EntityJson } from './entity.json'; import { EntityService } from './entity.service'; import { MatPaginator, MatSort, MatTableDataSource } from '@angular/material'; import { Observable } from 'rxjs/Observable'; import { merge } from 'rxjs/observable/merge'; import { of as observableOf } from 'rxjs/observable/of'; import { catchError } from 'rxjs/operators/catchError'; import { map } from 'rxjs/operators/map'; import { startWith } from 'rxjs/operators/startWith'; import { switchMap } from 'rxjs/operators/switchMap'; @Component({ selector: 'entity-latest-page', providers: [EntityService], styles: [' :host mat-table { display: flex; flex-direction: column; min-width: 100px; max-width: 800px; margin: 0 auto; } '], template: '<mat-card> <mat-card-title>Entity List <button mat-button [routerLink]="['/create/entity']"> CREATE </button> </mat-card-title> <mat-card-content> <mat-table #table matSort [dataSource]="entitiesDataSource" matSort class="mat-elevation-z2"> <ng-container matColumnDef="id"> <mat-header-cell *matHeaderCellDef mat-sort-header> Id </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> <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row> </mat-table> </mat-card-content> <mat-card-content> <mat-paginator #paginator [length]="resultsLength" [pageSize]="5" [pageSizeOptions]="[5, 10, 20]"> </mat-paginator> </mat-card-content> </mat-card> ' }) export class EntityLatestPageComponent implements AfterViewInit { private entities: EntityJson[]; private entitiesDataSource: MatTableDataSource<EntityJson> = new MatTableDataSource(); private displayedColumns = ['id', 'name']; resultsLength = 0; isLoadingResults = false; isRateLimitReached = false; @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; public constructor( @Inject(EntityService) private entityService: EntityService) { } public ngAfterViewInit() { // If the user changes the sort order, reset back to the first page. this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); merge(this.sort.sortChange, this.paginator.page) .pipe( startWith({}), switchMap(() => { this.isLoadingResults = true; return this.entityService.fetchLatest(this.sort.active, this.sort.direction, this.paginator.pageIndex + 1, this.paginator.pageSize, (total) => this.resultsLength = total); }), map(data => { this.isLoadingResults = false; this.isRateLimitReached = false; //alternatively to response headers; //this.resultsLength = data.total; return data; }), catchError(() => { this.isLoadingResults = false; this.isRateLimitReached = true; return observableOf([]); }) ).subscribe(data => this.entitiesDataSource.data = data); } } 

Service:

 import { EntityJson } from './entity.json'; import { ApiHelper } from '../common/api.helper'; import { Http, Headers, Response, RequestOptions } from '@angular/http'; import { Inject, Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { AuthenticationService } from '../auth/authentication.service'; import { stringify } from 'query-string'; @Injectable() export class EntityService { private options: RequestOptions; private apiPrefix: string; private apiEndpoint: string; constructor( @Inject(Http) private http: Http, @Inject(AuthenticationService) private authService: AuthenticationService) { this.options = authService.prepareRequestHeaders(); this.apiPrefix = 'http://localhost:4200/api/v1/'; this.apiEndpoint = this.apiPrefix + 'entities'; } public fetchLatest(sort: string = '', order: string = '', page: number = 1, perPage: number = 5, initTotal: Function = () => {}): Observable<EntityJson[]> { return this.http.get(this.apiEndpoint +'?' + EntityService.createUrlQuery({sort: {field: sort, order: order}, pagination: { page, perPage }}), this.options) .map((res) => { const total = res.headers.get('x-total-count').split('/').pop(); initTotal(total); return JSON.parse(res.text()).content }); } //should be put in a util static createUrlQuery(params: any) { if (!params) { return ""; } let page; let perPage; let field; let order; let query: any = {}; if (params.pagination) { page = params.pagination.page; perPage = params.pagination.perPage; query.range = JSON.stringify([ page, perPage, ]); } if (params.sort) { field = params.sort.field; order = params.sort.order; if (field && order) { query.sort = JSON.stringify([field, order]); } else { query.sort = JSON.stringify(['id', 'ASC']); } } if (!params.filter) { params.filter = {}; } if (Array.isArray(params.ids)) { params.filter.id = params.ids; } if (params.filter) { query.filter = JSON.stringify(params.filter) } console.log(query, stringify(query)); return stringify(query); } } 

Spring Boot Rest Controller Endpoint

 @GetMapping("entities") public Iterable<Entity> filterBy( @RequestParam(required = false, name = "filter") String filterStr, @RequestParam(required = false, name = "range") String rangeStr, @RequestParam(required = false, name="sort") String sortStr) { //my own helpers - for source: https://github.com/zifnab87/react-admin-java-rest //FilterWrapper wrapper = filterService.extractFilterWrapper(filterStr, rangeStr, sortStr); //return filterService.filterBy(wrapper, repo); } 

Some notes:

  1. Make sure you import the modules: MatTableModule , MatPaginatorModule and MatSortModule along with other modules from Material Design.
  2. I decided to populate the resultsLength (total) from the Response-Header x-total-count which I populated through Spring Boot @ControllerAdvice . Alternatively, you can get this information from an object returned from an EntityService (for example, Page for Spring Boot), although this implies that you will need to use any as the return type or declare wrapper class objects for all entities in your project if you want to be type safe.
+9
source

I figured out this problem by following the Table retrieving data via HTTP from corner materials.

In the ngAfterViewInit() example, use ngAfterViewInit() plus the observables to handle everything on the table, paginate, sort, and other things you need code:

 import {Component, AfterViewInit, ViewChild} from '@angular/core'; import {HttpClient} from '@angular/common/http'; import {MatPaginator, MatSort, MatTableDataSource} from '@angular/material'; import {Observable} from 'rxjs/Observable'; import {merge} from 'rxjs/observable/merge'; import {of as observableOf} from 'rxjs/observable/of'; import {catchError} from 'rxjs/operators/catchError'; import {map} from 'rxjs/operators/map'; import {startWith} from 'rxjs/operators/startWith'; import {switchMap} from 'rxjs/operators/switchMap'; /** * @title Table retrieving data through HTTP */ @Component({ selector: 'table-http-example', styleUrls: ['table-http-example.css'], templateUrl: 'table-http-example.html', }) export class TableHttpExample implements AfterViewInit { displayedColumns = ['created', 'state', 'number', 'title']; exampleDatabase: ExampleHttpDao | null; dataSource = new MatTableDataSource(); resultsLength = 0; isLoadingResults = false; isRateLimitReached = false; @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; constructor(private http: HttpClient) {} ngAfterViewInit() { this.exampleDatabase = new ExampleHttpDao(this.http); // If the user changes the sort order, reset back to the first page. this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); merge(this.sort.sortChange, this.paginator.page) .pipe( startWith({}), switchMap(() => { this.isLoadingResults = true; return this.exampleDatabase!.getRepoIssues( this.sort.active, this.sort.direction, this.paginator.pageIndex); }), map(data => { // Flip flag to show that loading has finished. this.isLoadingResults = false; this.isRateLimitReached = false; this.resultsLength = data.total_count; return data.items; }), catchError(() => { this.isLoadingResults = false; // Catch if the GitHub API has reached its rate limit. Return empty data. this.isRateLimitReached = true; return observableOf([]); }) ).subscribe(data => this.dataSource.data = data); } } export interface GithubApi { items: GithubIssue[]; total_count: number; } export interface GithubIssue { created_at: string; number: string; state: string; title: string; } /** An example database that the data source uses to retrieve data for the table. */ export class ExampleHttpDao { constructor(private http: HttpClient) {} getRepoIssues(sort: string, order: string, page: number): Observable<GithubApi> { const href = 'https://api.github.com/search/issues'; const requestUrl = '${href}?q=repo:angular/material2&sort=${sort}&order=${order}&page=${page + 1}'; return this.http.get<GithubApi>(requestUrl); } } 

See that everything is handled internally by ngAfterViewInit thanks to observables. string this.resultsLength = data.total_count; expects your service to return data with a total number of registers, in my case I use Springboot and it returns everything I need.

If you need more clarification, write any comment, and I'm going to update the answer, but by checking the example from the documentation, you will understand this.

+6
source

This is a combination of Mikhail Mikhailidis’s answer and the official table parsing example , compressed into a single file and using a dummy "network" class of service, which returns an Observable and models the delay.

If you have launched and run the Material 2 + Angular 5 project, you can add it to the new component file, add it to the list of modules and start hacking. At the very least, this should be a lower barrier to entry.

 import { ViewChild, Component, Inject, OnInit, AfterViewInit } from '@angular/core'; import { MatPaginator, MatSort, MatTableDataSource } from '@angular/material'; import { Observable } from 'rxjs/Observable'; import { merge } from 'rxjs/observable/merge'; import { of as observableOf } from 'rxjs/observable/of'; import { catchError } from 'rxjs/operators/catchError'; import { map } from 'rxjs/operators/map'; import { startWith } from 'rxjs/operators/startWith'; import { switchMap } from 'rxjs/operators/switchMap'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; @Component({ selector: 'app-element-table', styles: [' :host mat-table { display: flex; flex-direction: column; min-width: 100px; max-width: 800px; margin: 0 auto; } '], template: ' <mat-card> <mat-card-title>Element List</mat-card-title> <mat-card-content> <mat-table #table matSort [dataSource]="elementDataSource" class="mat-elevation-z2"> <!-- Position Column --> <ng-container matColumnDef="position"> <mat-header-cell *matHeaderCellDef mat-sort-header> No. </mat-header-cell> <mat-cell *matCellDef="let element"> {{element.position}} </mat-cell> </ng-container> <!-- Name Column --> <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> <!-- Weight Column --> <ng-container matColumnDef="weight"> <mat-header-cell *matHeaderCellDef mat-sort-header> Weight </mat-header-cell> <mat-cell *matCellDef="let element"> {{element.weight}} </mat-cell> </ng-container> <!-- Symbol Column --> <ng-container matColumnDef="symbol"> <mat-header-cell *matHeaderCellDef mat-sort-header> Symbol </mat-header-cell> <mat-cell *matCellDef="let element"> {{element.symbol}} </mat-cell> </ng-container> <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row> </mat-table> </mat-card-content> <mat-card-content> <mat-paginator #paginator [length]="resultsLength" [pageSize]="5" [pageSizeOptions]="[5, 10, 20]" showFirstLastButtons> </mat-paginator> </mat-card-content> </mat-card> ' }) export class ElementTableComponent implements AfterViewInit { public elementDataSource = new MatTableDataSource<PeriodicElement>(); public displayedColumns = ['position', 'name', 'weight', 'symbol']; private entities: PeriodicElement[]; private elementService = new ElementService(); resultsLength = 0; isLoadingResults = false; isRateLimitReached = false; @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; public constructor() { } public ngAfterViewInit() { this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); merge(this.sort.sortChange, this.paginator.page) .pipe( startWith({ data: [], resultsLength: 0 } as ElementResult), switchMap(() => { this.isLoadingResults = true; return this.elementService.fetchLatest( this.sort.active, this.sort.direction, this.paginator.pageIndex + 1, this.paginator.pageSize); }), map(result => { this.isLoadingResults = false; this.isRateLimitReached = false; this.resultsLength = result.resultsLength; return result.data; }), catchError(() => { this.isLoadingResults = false; this.isRateLimitReached = true; return observableOf([]); }) ).subscribe(data => this.elementDataSource.data = data); } } // Simulates server-side rendering class ElementService { constructor() { } fetchLatest(active: string, direction: string, pageIndex: number, pageSize: number): Observable<ElementResult> { active = active || 'position'; const cmp = (a, b) => (a[active] < b[active] ? -1 : 1); const rev = (a, b) => cmp(b, a); const [l, r] = [(pageIndex - 1) * pageSize, pageIndex * pageSize]; const data = [...ELEMENT_DATA] .sort(direction === 'desc' ? rev : cmp) .filter((_, i) => l <= i && i < r); // 1 second delay to simulate network request delay return new BehaviorSubject({ resultsLength: ELEMENT_DATA.length, data }).debounceTime(1000); } } interface ElementResult { resultsLength: number; data: PeriodicElement[]; } export interface PeriodicElement { name: string; position: number; weight: number; symbol: string; } const ELEMENT_DATA: PeriodicElement[] = [ { position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H' }, { position: 2, name: 'Helium', weight: 4.0026, symbol: 'He' }, { position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li' }, { position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be' }, { position: 5, name: 'Boron', weight: 10.811, symbol: 'B' }, { position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C' }, { position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N' }, { position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O' }, { position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F' }, { position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne' }, { position: 11, name: 'Sodium', weight: 22.9897, symbol: 'Na' }, { position: 12, name: 'Magnesium', weight: 24.305, symbol: 'Mg' }, { position: 13, name: 'Aluminum', weight: 26.9815, symbol: 'Al' }, { position: 14, name: 'Silicon', weight: 28.0855, symbol: 'Si' }, { position: 15, name: 'Phosphorus', weight: 30.9738, symbol: 'P' }, { position: 16, name: 'Sulfur', weight: 32.065, symbol: 'S' }, { position: 17, name: 'Chlorine', weight: 35.453, symbol: 'Cl' }, { position: 18, name: 'Argon', weight: 39.948, symbol: 'Ar' }, { position: 19, name: 'Potassium', weight: 39.0983, symbol: 'K' }, { position: 20, name: 'Calcium', weight: 40.078, symbol: 'Ca' }, ]; 

By the way, this question on material 2 about filtering can be useful if you want to implement filtering yourself.

+2
source

I am enclosing a brilliant complete guide on how to do it right.

https://blog.angular-university.io/angular-material-data-table/

0
source

Source: https://habr.com/ru/post/1274157/


All Articles