我正在尝试实现Angular Material 2,MatPaginator服务器端分页..我该如何实现?
以下是代码示例:
<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>
分页组件:
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'},
];
如何实现服务器端分页,这将触发下一页点击或页面大小更改的更改事件以获取下一组记录。
https://stackblitz.com/angular/qxxpqbqolyb?file=app%2Ftable-pagination-example.ts
有人请吗?
答案 0 :(得分:8)
根据Wilfredo的回答(https://stackoverflow.com/a/47994113/986160),我编写了一个完整的工作示例,因为有些文章也没有问题。以下是使用Angular 5和Material Design进行服务器端分页和排序的更一般情况(仍然需要插入过滤) - 希望它对某人有所帮助:
分页组件:
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);
}
}
<强>服务强>
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控制器端点
@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);
}
有些说明:
MatTableModule
,
来自Material Design的其他模块的MatPaginatorModule
和MatSortModule
。resultsLength
中填充x-total-count
(总计),我通过Spring Boot @ControllerAdvice
填充。或者,您可以从EntityService
返回的对象(例如Page
for Spring Boot)获取此信息,但这意味着您需要使用any
作为返回类型或为所有人声明包装类对象项目中的实体,如果你想成为&#34;类型安全&#34;。答案 1 :(得分:6)
我在角度材料文档中找到了Table retrieving data through HTTP之后的这个问题。
示例说明的是,使用ngAfterViewInit()
加上observable来处理表格中的所有内容,分页,排序和其他所需的内容,代码:
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);
}
}
由于可观察性,看看在ngAfterViewInit中处理了所有内容。第this.resultsLength = data.total_count;
行期望您的服务返回总寄存器数的数据,在我的情况下,我使用springboot并返回我需要的所有内容。
如果您需要更多说明,请撰写任何评论,我会更新答案,但请查看您将要记录的文档中的示例。
答案 2 :(得分:2)
这是Michail Michailidis's answer和official table pagination example的组合,缩小为单个文件,并使用模拟&#34;网络&#34;返回Observable并模拟延迟的服务类。
如果您已启动并运行Material 2 + Angular 5项目,您应该可以将其放入新的组件文件中,将其添加到模块列表中,然后开始入侵。至少它应该是进入入门的较低障碍。
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' },
];
顺便说一下,this issue on material2 about filtering如果您希望自己实施过滤,可能会有用。