过滤后更新 Angular 分页器?

Update Angular paginator after filtering?

我在 Angular 中有一个自定义数据源,我在其中实现了排序、分页和过滤。它们都工作正常,但是当我去过滤 table 中的数据时,它只显示当前页面上存在的数据的对应关系。过滤器适用于所有数据,但分页器不会更新 table 并且不会显示适当减少的数据。

这是代码

数据模型

import { Sistema } from './sistema'

export class Galassia {

  id: number;
  nome: string;
  imgLink: string;
  sistema: Sistema[];

}

服务-rest.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { Galassia } from '../model/galassia';
import { catchError, tap } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class ServizioProdottiRESTService {


  galassie: Galassia[];

  prodottiUrl = 'http://localhost:8080/prodotti';

  constructor(private http: HttpClient) { }

  getGalassie(): Observable<Galassia[]> {
    return this.http.get<Galassia[]>(this.prodottiUrl + '/galassie').pipe(
    tap(galaxy => this.galassie = galaxy),
    catchError(this.handleError('getGalassie', []))
  );
  }

  private handleError<T>(operation = 'operation', result?: T) {
    return (error: any): Observable<T> => {
        console.log(error);
        this.log(`${operation} failed: ${error.message}`);
        return of(result as T);
    };
  }

  private log(message: string) {
    console.log('HeroService: ' + message);
  }
}

component.ts

import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTable } from '@angular/material/table';
import { ListaProdottiDataSource } from './lista-prodotti-datasource';
import { ServizioProdottiRESTService } from '../servizio-prodotti-rest.service';
import { Galassia } from '../../model/galassia';

@Component({
  selector: 'app-lista-prodotti',
  templateUrl: './lista-prodotti.component.html',
  styleUrls: ['./lista-prodotti.component.css']
})
export class ListaProdottiComponent implements AfterViewInit, OnInit {

  @ViewChild(MatPaginator) paginator: MatPaginator;
  @ViewChild(MatSort) sort: MatSort;
  @ViewChild(MatTable) table: MatTable<Galassia>;

  dataSource: ListaProdottiDataSource;
  displayedColumns = ['imgLink' ,'nome', 'button'];

  constructor(private myService: ServizioProdottiRESTService) {}

  ngOnInit() {
    this.dataSource = new ListaProdottiDataSource(this.myService);
  }

  ngAfterViewInit() {
    this.table.dataSource = this.dataSource;
    this.dataSource.sort = this.sort;
    this.dataSource.paginator = this.paginator;
  }

  applyFilter(event: Event) {
    const filterValue = (event.target as HTMLInputElement).value;
    this.dataSource.filter = filterValue.trim().toLowerCase();
  }
}

component.html

<div class="mat-elevation-z8 table">

  <table mat-table class="full-width-table" matSort aria-label="Elements" [dataSource]="dataSource.data">

    <!-- imgLink Column -->
    <ng-container matColumnDef="imgLink">
      <th mat-header-cell *matHeaderCellDef></th>
      <td mat-cell *matCellDef="let row"><img [src]="row.imgLink"/></td>
    </ng-container>

    <!-- Name Column -->
    <ng-container matColumnDef="nome">
      <th mat-header-cell *matHeaderCellDef mat-sort-header>Nome</th>
      <td mat-cell *matCellDef="let row">{{row.nome}}</td>
    </ng-container>

    <!-- Button Column -->
    <ng-container matColumnDef="button">
      <th mat-header-cell *matHeaderCellDef><div class="example-header">
        <mat-form-field>
          <input matInput (keyup)="applyFilter($event)" placeholder="Cosa vuoi cercare?">
        </mat-form-field>
      </div></th>
      <td mat-cell *matCellDef="let row"><img id="freccia" src="../../../assets/freccia.png"></td>
    </ng-container>

    <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
    <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
  </table>

  <mat-paginator #paginator
      [length]="dataSource?.data.length"
      [pageIndex]="0"
      [pageSize]="5"
      [pageSizeOptions]="[5, 10, 15]">
  </mat-paginator>

</div>

和datasource.ts

import { DataSource } from '@angular/cdk/collections';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { map } from 'rxjs/operators';
import { Observable, of as observableOf, merge, BehaviorSubject } from 'rxjs';
import { ServizioProdottiRESTService } from '../servizio-prodotti-rest.service';
import { Galassia } from '../../model/galassia';

export class ListaProdottiDataSource extends DataSource<Galassia> {

  data: Galassia[];
  paginator: MatPaginator;
  sort: MatSort;

  get filter(): string { return this.filter$.getValue(); }
  set filter(value: string) { this.filter$.next(value); }
  filter$: BehaviorSubject<string> = new BehaviorSubject<string>('');

  constructor(private myService: ServizioProdottiRESTService) {
    super();
    this.myService.getGalassie().subscribe(galaxy => this.data = galaxy);
  }


  connect(): Observable<Galassia[]> {

    const dataMutations = [
      observableOf(this.data),
      this.paginator.page,
      this.sort.sortChange,
      this.filter$
    ];

    return merge(...dataMutations).pipe(map(() => {
      return this.getPagedData(this.getSortedData(this.getFilteredData([...this.data])));
    }));
  }

  disconnect() {}

  private getFilteredData(data: Galassia[]) {
    return data.filter(d => d.nome.toLowerCase().includes(this.filter));
  }


  private getPagedData(data: Galassia[]) {
    const startIndex = this.paginator.pageIndex * this.paginator.pageSize;
    return data.splice(startIndex, this.paginator.pageSize);
  }

  private getSortedData(data: Galassia[]) {
    if (!this.sort.active || this.sort.direction === '') {
      return data;
    }

    return data.sort((a, b) => {
      const isAsc = this.sort.direction === 'asc';
      switch (this.sort.active) {
        case 'nome': return compare(a.nome, b.nome, isAsc);
        default: return 0;
      }
    });
  }
}

function compare(a: string | number, b: string | number, isAsc: boolean) {
  return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
}

问题的一些图片

This is the first page before filtering

This is the second page before filtering

当我在引用第二页元素的第一页上应用过滤器时,会发生这种情况

First page after filtering

该页面是空的,但如果我转到第二页,则会显示该元素

Second page after filtering

正如您还可以从页面索引中看到的那样,这些索引没有更新,因为它们基于完整的元素数组,这是另一个问题

我通过在构造函数中使用我的主数组的值初始化它并在 getFilteredData 方法中更新分页长度来声明一个包含过滤数据的临时数组,从而解决了这个问题。另外我改变了connect()方法中方法的调用顺序

//declaration
filteredData: Galassia[];

constructor(private myService: ServizioProdottiRESTService) {
super();
this.myService.getGalassie().subscribe(galaxy => this.data = galaxy);

//Initialization
this.filteredData = this.data;
}

//Change the order of the calls
return this.getPagedData(this.getFilteredData(this.getSortedData([...this.data])));

//update getFilteredData method
private getFilteredData(data: Galassia[]) {
    this.filteredData = data.filter(d => d.nome.toLowerCase().includes(this.filter));
    this.paginator.length = this.filteredData.length;
    return this.filteredData;
}

我不知道这是否是最好的方法,但它有效

您的原始解决方案是正确的,您只需在调用 getPagedData 之前设置分页器的长度即可。更新后的连接方法应该是这样的:

  connect(): Observable<Galassia[]> {

    const dataMutations = [
      observableOf(this.data),
      this.paginator.page,
      this.sort.sortChange,
      this.filter$
    ];


    return merge(...dataMutations).pipe(
      map(() => this.getSortedData(this.getFilteredData([...this.data]))),
      tap(data => this.paginator.length = data.length),
      map(data => this.getPagedData(data))
    );
  }