Angular Material Table使用后端HTTP请求返回的Observable数据时无法更新显示的问题求助
我尝试将从后端获取的数据展示在Angular Material Table中。当我的_timelogService.getAllTimelogs()方法返回硬编码的模拟数据的Observable时,一切正常。但当我改为向后端发起实际的HTTP GET请求并返回该数据的Observable时,尽管dataSource中包含所需数据,HTML表格却无法显示任何行。即使使用实际的Observable,调用logData()方法时也能在控制台打印出正确的dataSource数据。我怀疑Angular Table没有在dataSource更新时同步更新,请问有人遇到过类似问题吗?
组件代码
export class OverviewComponent implements OnInit { displayedColumns: string[] = ['roomId', 'startTime', 'endTime', 'duration']; dataSource = new MatTableDataSource<TimeLogTable>(); @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; @ViewChild(MatSort, { static: true }) sort: MatSort; constructor(private _timelogService: TimelogService) { //In the Docs example the dummy data gets created here } ngOnInit(): void { this._timelogService.getAllTimelogs().subscribe( dataObs=>{ console.log("Display received data"); console.log(dataObs); this.dataSource.data = dataObs; this.dataSource.paginator = this.paginator; this.dataSource.sort = this.sort; } ) } logData(){ console.log(this.dataSource); } }
服务代码
getAllTimelogs(): Observable<TimeLogTable[]>{ var timelogsArr: TimeLogTable[] = []; var timelogsArrDummy: TimeLogTable[] = []; //Call to backend this._http.get<any>('http://localhost:3000/gettimelogs?token=' + localStorage.getItem('token'), this.httpOptions) .subscribe( res=>{ //Create timeLogTable from json with backend response for (let i = 0; i < res.timelogs.length; i++) { timelogsArr.push({ roomId: res.timelogs[i].roomId, startTime: new Date(res.timelogs[i].startTime), endTime: new Date (res.timelogs[i].endTime), duration: "test" }); } }, err=>{ //Error handling } ); //Return the Observable - DOES NOT WORK return of (timelogsArr); /* //Return the dummy data as an Observable - DOES WORK //Dummy data timelogsArrDummy = [ {roomId: 'kit_A', startTime: new Date(1631185600000), endTime: new Date (1631185661311), duration: "test"}, {roomId: 'din_A', startTime: new Date(1631185600000), endTime: new Date (1631195661311), duration: "test"}, {roomId: 'off_A', startTime: new Date(1631185600000), endTime: new Date (1632185661311), duration: "test"}, {roomId: 'kit_B', startTime: new Date(1631185600000), endTime: new Date (1631885661311), duration: "test"}, ] return of (timelogsArrDummy); */ }
问题分析与解决方法
兄弟,我一眼就看出问题出在你的服务代码里了!你犯了一个典型的异步操作错误:
- 你在服务里先订阅了HTTP请求,然后立刻返回了
of(timelogsArr)。但HTTP请求是异步的,这时候timelogsArr还是空数组,返回的Observable已经把空数组发出去了。等之后HTTP请求完成,你把数据塞进timelogsArr,但组件订阅的那个Observable早就完成了,根本不会收到更新后的数据。
正确的服务写法
不要在服务里订阅HTTP请求,而是用RxJS的map操作符来转换后端返回的数据,直接返回这个经过处理的Observable,让组件去订阅:
import { map } from 'rxjs/operators'; getAllTimelogs(): Observable<TimeLogTable[]>{ // 直接返回HTTP请求的Observable,用map处理数据 return this._http.get<any>('http://localhost:3000/gettimelogs?token=' + localStorage.getItem('token'), this.httpOptions) .pipe( map(res => { // 在这里把后端返回的数据转换成TimeLogTable数组 const timelogsArr: TimeLogTable[] = []; for (let i = 0; i < res.timelogs.length; i++) { timelogsArr.push({ roomId: res.timelogs[i].roomId, startTime: new Date(res.timelogs[i].startTime), endTime: new Date(res.timelogs[i].endTime), duration: "test" }); } return timelogsArr; }), // 这里可以加catchError处理错误 ); }
为什么这样能行?
HTTP请求本身就是一个Observable,当后端返回数据时,它才会发出值。我们用map操作符在数据流里转换数据,这样返回的Observable会在请求完成时发出转换后的完整数组。组件订阅这个Observable时,就能拿到正确的数据,赋值给dataSource.data后,Material Table就能正常渲染了。
另外,你组件里的写法没问题,只要服务返回正确的Observable,订阅后赋值给dataSource就能正常显示。如果你想更简洁,还可以用Angular的async管道,直接在模板里订阅Observable,这样连手动订阅都省了:
比如组件里定义timelogs$: Observable<TimeLogTable[]>;,然后ngOnInit里this.timelogs$ = this._timelogService.getAllTimelogs();,模板里<table mat-table [dataSource]="timelogs$ | async" ...>,这样还能避免内存泄漏~
内容的提问来源于stack exchange,提问作者Jonas

