You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular应用中分页与过滤管道不同步的问题及解决方案

问题:Angular分页与过滤不同步

背景

我正在开发一个基于Angular和Random User API的“用户应用”,核心功能是展示用户列表、按国家过滤和分页,但过滤后分页页码没有同步更新。

相关代码

用户服务(services/user.service.ts)

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/internal/Observable';

@Injectable()
export class UserService {
  apiURL: string =
    'https://randomuser.me/api/?&results=18&inc=id,name,location';

  constructor(private httpClient: HttpClient) {}

  public getUsers(): Observable<any[]> {
    return this.httpClient.get<any[]>(`${this.apiURL}`);
  }
}

分页组件(pagination-basic)

import { Component } from '@angular/core';
import { UserService } from './services/user.service';
import { Countries } from './data/countries';

@Component({
  selector: 'ngbd-pagination-basic',
  styleUrls: ['./pagination-basic.css'],
  templateUrl: './pagination-basic.html',
  providers: [UserService],
})
export class NgbdPaginationBasic {
  public page: number = 1;
  public pageSize: number = 10;
  public totalRecords: number = 0;

  public users: any[] = [];
  public countries: any[] = [];
  public country: string = '';

  constructor(private userService: UserService) {}

  public getCountries() {
    this.countries = Countries;
  }

  public getUsers() {
    this.userService.getUsers().subscribe((response) => {
      this.users = response.results;
      this.totalRecords = this.users?.length;
    });
  }

  ngOnInit(): void {
    this.getCountries();
    this.getUsers();
  }
}

组件模板(pagination-basic.html)

<div class="container">
  <h1 class="display-4">Users</h1>

  <select [(ngModel)]="country" class="form-control my-2">
    <option selected value="">Filter by country</option>
    <option *ngFor="let c of countries" [value]="c.name">{{ c.name }}</option>
  </select>

  <div *ngIf="users?.length" class="card my-3">
    <div class="card-body p-0">
      <table class="m-0 table table-striped">
        <tr>
          <th>Name</th>
          <th>Country</th>
          <th>City</th>
        </tr>
        <tr
          *ngFor="let user of users | usersFilter: country | slice: (page - 1) * pageSize : (page-1) * pageSize + pageSize"
        >
          <td class="text-nowrap">
            {{ user.name.first }} {{ user.name.last }}
          </td>
          <td>{{user.location.country}}</td>
          <td>{{user.location.city}}</td>
        </tr>
      </table>
    </div>

    <div class="card-footer bg-white d-flex justify-content-center">
      <ngb-pagination
        size="sm"
        [pageSize]="pageSize"
        [collectionSize]="totalRecords"
        [(page)]="page"
        class="m-0"
      ></ngb-pagination>
    </div>
  </div>

  <p class="text-center" *ngIf="!users?.length">No users found! :(</p>
</div>

过滤管道(usersFilter)

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'usersFilter',
})
export class UsersFilterPipe implements PipeTransform {
  transform(input: any[], country: string): any[] {
    let output: any[] = [];

    if (country == '') output = input;
    else output = input.filter((user) => user.location.country == country);

    return output;
  }
} 

问题描述

按国家过滤结果时,符合条件的用户数量已经减少,但分页的页码数没有同步更新。比如选择加拿大后只显示1个用户,但分页仍显示2个页码,分页和过滤状态不同步。

疑问

  1. 我哪里操作有误?
  2. 实现分页与过滤同步的最可靠方法是什么?

解答

1. 错误原因

你当前的totalRecords仅在初始化获取用户时设置为原始用户数组的长度,后续过滤操作不会更新这个值。而分页组件ngb-pagination依赖的collectionSize绑定的是totalRecords,所以过滤后分页的总记录数还是原始的18条,导致页码数没有变化。

另外,模板里用管道过滤后的数据只是在视图层展示,但组件逻辑里没有跟踪过滤后的实际数量,这就造成了视图展示的过滤结果和分页计算的总记录数不一致。

2. 可靠的实现方法

推荐采用组件内维护过滤后数据的方案,符合Angular性能优化和逻辑内聚的最佳实践:

具体实现步骤
  1. 在组件中新增两个数组:originalUsers存储原始用户数据,filteredUsers存储过滤后的用户数据
  2. 把过滤逻辑从管道移到组件内部,监听country的变化实时更新过滤结果和总记录数
  3. 过滤后自动重置页码到第一页,避免出现空页面

修改后的组件代码:

import { Component } from '@angular/core';
import { UserService } from './services/user.service';
import { Countries } from './data/countries';

@Component({
  selector: 'ngbd-pagination-basic',
  styleUrls: ['./pagination-basic.css'],
  templateUrl: './pagination-basic.html',
  providers: [UserService],
})
export class NgbdPaginationBasic {
  public page: number = 1;
  public pageSize: number = 10;
  public totalRecords: number = 0;

  public originalUsers: any[] = [];
  public filteredUsers: any[] = [];
  public countries: any[] = [];
  public country: string = '';

  constructor(private userService: UserService) {}

  public getCountries() {
    this.countries = Countries;
  }

  public getUsers() {
    this.userService.getUsers().subscribe((response) => {
      this.originalUsers = response.results;
      this.filterUsers();
    });
  }

  private filterUsers() {
    if (!this.country) {
      this.filteredUsers = [...this.originalUsers];
    } else {
      this.filteredUsers = this.originalUsers.filter(user => user.location.country === this.country);
    }
    this.totalRecords = this.filteredUsers.length;
    this.page = 1;
  }

  ngOnInit(): void {
    this.getCountries();
    this.getUsers();
  }
}

修改后的模板代码(移除管道,改用filteredUsers):

<div class="container">
  <h1 class="display-4">Users</h1>

  <select [(ngModel)]="country" class="form-control my-2" (change)="filterUsers()">
    <option selected value="">Filter by country</option>
    <option *ngFor="let c of countries" [value]="c.name">{{ c.name }}</option>
  </select>

  <div *ngIf="filteredUsers?.length" class="card my-3">
    <div class="card-body p-0">
      <table class="m-0 table table-striped">
        <tr>
          <th>Name</th>
          <th>Country</th>
          <th>City</th>
        </tr>
        <tr
          *ngFor="let user of filteredUsers | slice: (page - 1) * pageSize : (page-1) * pageSize + pageSize"
        >
          <td class="text-nowrap">
            {{ user.name.first }} {{ user.name.last }}
          </td>
          <td>{{user.location.country}}</td>
          <td>{{user.location.city}}</td>
        </tr>
      </table>
    </div>

    <div class="card-footer bg-white d-flex justify-content-center">
      <ngb-pagination
        size="sm"
        [pageSize]="pageSize"
        [collectionSize]="totalRecords"
        [(page)]="page"
        class="m-0"
      ></ngb-pagination>
    </div>
  </div>

  <p class="text-center" *ngIf="!filteredUsers?.length">No users found! :(</p>
</div>

如果一定要保留管道,也可以通过在组件中动态计算过滤后的总数绑定到collectionSize,但这种方法会频繁触发变更检测,数据量大时性能较差,不推荐使用。


内容的提问来源于stack exchange,提问作者Razvan Zamfir

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 11:12:01