source

Angular Material 2 테이블 서버 측면 페이지

nicesource 2023. 7. 3. 22:58
반응형

Angular Material 2 테이블 서버 측면 페이지

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

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);
    }
}

스프링 부트레스트 컨트롤러 끝점

@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);
}

참고 사항:

  1. 모듈을 가져오는지 확인합니다.MatTableModule,MatPaginatorModule그리고.MatSortModule재료 설계의 다른 모듈과 함께.
  2. 나는 사람들을 채우기로 결정했습니다.resultsLength(합계) 반응-머리글에서x-total-count내가 스프링 부츠를 통해 채우는.@ControllerAdvice또는 반환된 개체에서 이 정보를 얻을 수 있습니다.EntityService(예:PageSpring Boot의 경우)를 사용해야 합니다.any"유형 안전"을 원하는 경우 프로젝트의 모든 엔티티에 대해 래퍼 클래스 개체를 반환하거나 선언합니다.

저는 표에서 각 물질 문서에서 HTTP로 데이터를 검색한 후 이 문제를 파악했습니다.

예제에서 말하는 것은 다음과 같습니다.ngAfterViewInit()테이블, 페이지, 정렬 및 기타 필요한 항목을 모두 처리할 수 있는 관찰 가능한 기능, 코드:

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);
  }
}

모든 것이 ngAfterView 내부에서 처리되는지 확인합니다.관찰 가능한 것들 덕분에 그 안에.전선this.resultsLength = data.total_count;귀하의 서비스가 총 레지스터 수가 포함된 데이터를 반환할 것으로 예상하고 있습니다. 제 경우 스프링부트를 사용하고 있는데 필요한 모든 것을 반환했습니다.

더 명확한 설명이 필요하면 의견을 작성하면 답변을 업데이트할 것입니다. 하지만 문서에서 예제를 확인하면 해결할 수 있습니다.

이는 Michaail Michaailidis의 답변공식페이지 예제를 결합한 것으로, 단일 파일로 요약되고 관찰 가능한 항목을 반환하고 지연 시간을 시뮬레이션하는 모의 "네트워크" 서비스 클래스를 사용합니다.

재료 2 + 각도 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' },
];

그나저나 필터링에 대한 문제는 필터링을 직접 구현하려는 경우 유용할 수 있습니다.

언급URL : https://stackoverflow.com/questions/47871840/angular-material-2-table-server-side-pagination

반응형