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. 错误原因
你当前的totalRecords仅在初始化获取用户时设置为原始用户数组的长度,后续过滤操作不会更新这个值。而分页组件ngb-pagination依赖的collectionSize绑定的是totalRecords,所以过滤后分页的总记录数还是原始的18条,导致页码数没有变化。
另外,模板里用管道过滤后的数据只是在视图层展示,但组件逻辑里没有跟踪过滤后的实际数量,这就造成了视图展示的过滤结果和分页计算的总记录数不一致。
2. 可靠的实现方法
推荐采用组件内维护过滤后数据的方案,符合Angular性能优化和逻辑内聚的最佳实践:
具体实现步骤
- 在组件中新增两个数组:
originalUsers存储原始用户数据,filteredUsers存储过滤后的用户数据 - 把过滤逻辑从管道移到组件内部,监听
country的变化实时更新过滤结果和总记录数 - 过滤后自动重置页码到第一页,避免出现空页面
修改后的组件代码:
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
相关产品推荐
相关产品推荐

