Angular Material Mat-table无法显示REST API数据,分页排序失效
Fixing Mat-table Pagination & Sorting Issues with Async Data
Let's break down what's going wrong with your implementation and fix it step by step:
Key Issues in Your Code
- ViewChild Timing: You're assigning
paginatorandsortinngOnInit, but withstatic: false, these elements aren't available until the view is initialized (usengAfterViewInitinstead). - Data Updates Not Triggering Table Refresh: Your
DataSourceusesobservableOf(this.data)which only emits once. When async data arrives later, the table has no way to detect the change. - Paginator Length Not Updated: The paginator's length is set only once in
connect, so it doesn't reflect the actual data count after the API call. - Unnecessary Observable Wrapping: Your service's
getAllAccountsmethod usesObservable.createunnecessarily, which adds complexity.
Fixed Code Implementations
1. Updated AccountListDataSource
We'll use a BehaviorSubject to track data changes and ensure the table gets notified when new data arrives:
import { DataSource } from '@angular/cdk/collections'; import { Observable, BehaviorSubject, merge, EMPTY } from 'rxjs'; import { map } from 'rxjs/operators'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; import { User } from './your-user-model-path'; // Replace with your actual User model path export class AccountListDataSource extends DataSource<User> { private dataSubject = new BehaviorSubject<User[]>([]); private _data: User[] = []; paginator: MatPaginator; sort: MatSort; constructor() { super(); } // Setter to update data and notify subscribers set data(data: User[]) { this._data = data; this.dataSubject.next(data); // Update paginator length when data changes if (this.paginator) { this.paginator.length = data.length; } } get data(): User[] { return this._data; } connect(): Observable<User[]> { // Combine data changes, paginator events, and sort events const dataMutations = [ this.dataSubject.asObservable(), this.paginator?.page || EMPTY, this.sort?.sortChange || EMPTY ]; return merge(...dataMutations).pipe( map(() => { return this.getPagedData(this.getSortedData([...this._data])); }) ); } disconnect() { this.dataSubject.complete(); } // Helper for sorting private getSortedData(data: User[]): User[] { if (!this.sort || !this.sort.active || this.sort.direction === '') { return data; } return data.sort((a, b) => { const isAsc = this.sort.direction === 'asc'; switch (this.sort.active) { case 'name': return compare(a.name, b.name, isAsc); case 'id': return compare(+a.id, +b.id, isAsc); // Add other columns as needed default: return 0; } }); } // Helper for pagination private getPagedData(data: User[]): User[] { if (!this.paginator) { return data; } const startIndex = this.paginator.pageIndex * this.paginator.pageSize; return data.slice(startIndex, startIndex + this.paginator.pageSize); } } // Generic compare function for sorting function compare(a: string | number, b: string | number, isAsc: boolean) { return (a < b ? -1 : 1) * (isAsc ? 1 : -1); }
2. Updated AccountListComponent
Move paginator/sort assignment to ngAfterViewInit and ensure async data updates trigger table refreshes:
import { Component, OnInit, AfterViewInit, ViewChild } from '@angular/core'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; import { MatTable } from '@angular/material/table'; import { AccountListDataSource } from './account-list-datasource'; import { AccountManagementService } from './account-management.service'; import { User } from './your-user-model-path'; // Replace with your model path @Component({ selector: 'app-account-list', templateUrl: './account-list.component.html', styleUrls: ['./account-list.component.css'] }) export class AccountListComponent implements OnInit, AfterViewInit { @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; @ViewChild(MatSort, {static: false}) sort: MatSort; @ViewChild(MatTable, {static: false}) table: MatTable<User>; dataSource: AccountListDataSource; displayedColumns = ['id', 'name']; constructor(private accountManagementService: AccountManagementService) { } ngOnInit() { this.dataSource = new AccountListDataSource(); this.getUsers(); } ngAfterViewInit() { // Assign paginator and sort once they're available this.dataSource.paginator = this.paginator; this.dataSource.sort = this.sort; // Trigger table render if needed this.table.renderRows(); } getUsers() { this.accountManagementService.getAllAccounts().subscribe((response: any) => { this.dataSource.data = response.content; console.log('Loaded data:', this.dataSource.data); // This will now show your data }, error => { console.error('Failed to fetch users:', error); }); } }
3. Simplified AccountManagementService
Remove the unnecessary Observable.create wrapper:
import { Observable } from 'rxjs'; import { ApiService } from './api.service'; // Your existing ApiService export class AccountManagementService { private PREFIX = 'accounts'; // Replace with your actual prefix constructor(private apiService: ApiService) { } getAllAccounts(): Observable<any> { return this.apiService.get({ endPoint: `/${this.PREFIX}/` }); } }
Why These Fixes Work
- BehaviorSubject: Tracks changes to your data and emits new values whenever the data is updated, ensuring the table refreshes automatically.
- ngAfterViewInit: Ensures
paginatorandsortare fully initialized before assigning them to the data source. - Paginator Length Update: The data setter updates the paginator's length whenever new data arrives, so pagination works correctly.
- Simplified Service: Removes redundant code and leverages the existing observable from your
ApiService.
Note: The reason you saw undefined when logging this.dataSource.data right after this.getUsers() is because the API call is asynchronous— the subscribe callback runs after ngOnInit completes. Logging inside the subscribe (as shown) will show the correct data once it's loaded.
内容的提问来源于stack exchange,提问作者atthijs98
相关产品推荐
相关产品推荐

