Angular组件无法动态展示HttpClient获取的数据问题排查
问题描述
我有一个用于展示Microsoft API数据的Angular组件,返回数据以自定义接口形式存储在Observable中。在组件HTML中通过*ngFor遍历对象生成mat grid tile,但组件首次初始化时完全不渲染,仅重新初始化(比如通过routerLink返回页面)时才正常显示数据。控制台显示数据已成功获取,但就是无法渲染到页面上,怀疑是生命周期钩子或Observable使用不当导致,该问题也出现在另一个组件中。
HTML代码
<mat-card class="wiki-page-card"> <h1 class="headline">Wiki Page</h1> <mat-divider [inset]="true"></mat-divider> <br> <h2 class="headline-h2">Below are a few helpful links to help get you started:</h2> <br> <mat-grid-list cols="8" rowHeight="100px" gutterSize="20px"> <mat-grid-tile *ngFor="let wikiPage of wikiObservable$ | async" [colspan]="1" [rowspan]="1" [style.background]="'black'" class="grid-style"> <a href="{{wikiPage.remoteUrl}}" target="_blank" class="text-inside-grid">{{wikiPage.name}}</a> </mat-grid-tile> </mat-grid-list> </mat-card>
TypeScript代码
import { Component, OnInit} from '@angular/core'; import { lastValueFrom } from 'rxjs'; import { WikiPage, WikiResponseValue, WikiSubpage } from 'src/app/shared/datasource/interfaces/wiki.interface'; import { WikiService } from 'src/app/shared/datasource/services/devops/wiki/wiki.service'; import { BehaviorSubject, Observable } from 'rxjs'; @Component({ selector: 'app-wikipage', templateUrl: './wikipage.component.html', styleUrls: ['./wikipage.component.scss'] }) export class WikiPageComponent implements OnInit { wikiObservable$ = new BehaviorSubject<WikiSubpage[]>([]) wikiPagesResult: WikiPage[] = [] wikiSubPages: WikiSubpage[] = [] constructor( private wikiService: WikiService, ){} async ngOnInit() { await (this.getWikiList()) } async getWikiList(){ const wikiPages$ = this.wikiService.getWikiPage(); let results = (await lastValueFrom(wikiPages$)).value results.forEach( async (wikiPage: WikiResponseValue) => { let wikiResult : WikiPage = { apiUrl : wikiPage.url, href : wikiPage.remoteUrl, name : wikiPage.name, projectId : wikiPage.projectId, type : wikiPage.type, wikiId : wikiPage.id } console.log(wikiResult) this.wikiPagesResult.push(wikiResult) this.getWikiPages(wikiResult) console.log(this.wikiSubPages) }, ) console.log(this.wikiPagesResult) } async getWikiPages(wikiPage: WikiPage){ const wikiSubPages$ = this.wikiService.getWikiSubPages(wikiPage.apiUrl) let results = (await lastValueFrom(wikiSubPages$)) results.subPages.forEach(async (subPage: WikiSubpage) =>{ console.log(subPage) let subPageDetails = await (this.wikiService.getWikiSubPagesDetail(subPage.url)) subPageDetails.forEach((fullPage: WikiSubpage) => { let pageName = (fullPage.path).replace('/', '') let subPageResult: WikiSubpage = { content : fullPage.content, gitItemPath : fullPage.gitItemPath, id : fullPage.id, name : pageName, remoteUrl : fullPage.remoteUrl, path : fullPage.path, url : fullPage.url, } this.wikiSubPages.push(subPageResult) }) }) console.log(this.wikiSubPages) this.wikiObservable$.next(this.wikiSubPages) console.log(this.wikiObservable$.value) } }
问题根源
核心问题在于异步代码的执行顺序错误:
getWikiList中使用async回调的forEach不会等待异步操作完成,导致getWikiPages还没执行完就继续后续逻辑;getWikiPages中同样用了带async回调的forEach,不等待子页面详情接口返回就调用wikiObservable$.next(),此时wikiSubPages还未被填充完整数据;- 首次初始化时BehaviorSubject推送的是空/不完整数组,而重新进入页面时数组已缓存了之前的完整数据,因此能正常显示。
修复方案
方案1:用for...of替代forEach处理异步循环
forEach不支持异步等待,换成for...of可确保每个异步操作完成后再执行下一步:
修改getWikiList:
async getWikiList(){ const wikiPages$ = this.wikiService.getWikiPage(); let results = (await lastValueFrom(wikiPages$)).value // 用for...of替代forEach,确保每个getWikiPages执行完成 for (const wikiPage of results) { let wikiResult : WikiPage = { apiUrl : wikiPage.url, href : wikiPage.remoteUrl, name : wikiPage.name, projectId : wikiPage.projectId, type : wikiPage.type, wikiId : wikiPage.id } this.wikiPagesResult.push(wikiResult) await this.getWikiPages(wikiResult) // 等待子页面数据获取完成 } }
修改getWikiPages:
async getWikiPages(wikiPage: WikiPage){ const wikiSubPages$ = this.wikiService.getWikiSubPages(wikiPage.apiUrl) let results = (await lastValueFrom(wikiSubPages$)) // 用for...of替代forEach,等待每个子页面详情获取完成 for (const subPage of results.subPages) { let subPageDetails = await this.wikiService.getWikiSubPagesDetail(subPage.url) subPageDetails.forEach((fullPage: WikiSubpage) => { let pageName = (fullPage.path).replace('/', '') let subPageResult: WikiSubpage = { content : fullPage.content, gitItemPath : fullPage.gitItemPath, id : fullPage.id, name : pageName, remoteUrl : fullPage.remoteUrl, path : fullPage.path, url : fullPage.url, } this.wikiSubPages.push(subPageResult) }) } // 所有数据获取完成后再推送更新,用展开运算符创建新数组触发变更检测 this.wikiObservable$.next([...this.wikiSubPages]) }
方案2:使用RxJS操作符组合数据流(更符合Angular风格)
避免混用async/await和Observable,直接用RxJS操作符处理异步流:
import { Component, OnInit} from '@angular/core'; import { BehaviorSubject, forkJoin, map, switchMap } from 'rxjs'; import { WikiPage, WikiResponseValue, WikiSubpage } from 'src/app/shared/datasource/interfaces/wiki.interface'; import { WikiService } from 'src/app/shared/datasource/services/devops/wiki/wiki.service'; @Component({ selector: 'app-wikipage', templateUrl: './wikipage.component.html', styleUrls: ['./wikipage.component.scss'] }) export class WikiPageComponent implements OnInit { wikiObservable$ = new BehaviorSubject<WikiSubpage[]>([]) constructor(private wikiService: WikiService) {} ngOnInit() { this.loadWikiData() } loadWikiData() { this.wikiService.getWikiPage().pipe( // 获取主页面列表 map(response => response.value), // 遍历每个主页面,获取子页面 switchMap(mainPages => { // 为每个主页面创建获取子页面的Observable const subPageObservables = mainPages.map(mainPage => { const wikiResult: WikiPage = { apiUrl : mainPage.url, href : mainPage.remoteUrl, name : mainPage.name, projectId : mainPage.projectId, type : mainPage.type, wikiId : mainPage.id }; // 获取当前主页面的子页面列表 return this.wikiService.getWikiSubPages(wikiResult.apiUrl).pipe( switchMap(subPageResponse => { // 为每个子页面获取详情 const detailObservables = subPageResponse.subPages.map(subPage => this.wikiService.getWikiSubPagesDetail(subPage.url) ); // 等待所有子页面详情获取完成 return forkJoin(detailObservables); }), // 处理子页面详情数据 map(details => { return details.flat().map(fullPage => { const pageName = fullPage.path.replace('/', ''); return { content : fullPage.content, gitItemPath : fullPage.gitItemPath, id : fullPage.id, name : pageName, remoteUrl : fullPage.remoteUrl, path : fullPage.path, url : fullPage.url, } as WikiSubpage; }); }) ); }); // 等待所有主页面的子页面数据获取完成 return forkJoin(subPageObservables); }), // 将所有子页面数据合并成一个数组 map(allSubPages => allSubPages.flat()) ).subscribe(allSubPages => { this.wikiObservable$.next(allSubPages); }); } }
额外注意点
- 推送数据到BehaviorSubject时,尽量创建新数组(如
[...this.wikiSubPages]),Angular变更检测依赖引用变化,直接修改原数组可能无法触发更新; - 可在组件初始化时添加加载状态,提升用户体验。
内容的提问来源于stack exchange,提问作者Andre du Toit
相关产品推荐
相关产品推荐

