Ionic/Angular中Firestore onSnapshot返回数据无法渲染到页面
问题说明
项目使用Ionic/Angular作为前端框架时出现视图不更新问题:Firestore可正常返回目标数据,文档变更时onSnapshot监听可正常获取更新数据,数据可正常打印到控制台,但始终无法渲染到<ion-content>组件中。
相关代码如下:
- 模板代码
<ion-content [fullscreen]="true" style="text-align: center;" *ngIf="!this.isLoading"> <ion-item *ngFor="let team of currentTeams; let index" [color]="team.color" style="margin: 10px;"> <p>{{index}}</p> <h1 slot="start" style="height: 3.5rem;">{{ team.name }}</h1> <br /> <div id="controls" slot="end"> <ion-icon style="margin: 0 20px" name="remove-circle" (click)="decrementTeamScore(team)"></ion-icon> <span>{{ team.score }}</span> <ion-icon style="margin: 0 20px" name="add-circle" (click)="incrementTeamScore(team)"></ion-icon> </div> </ion-item> </ion-content>
- 类文件逻辑代码
this.isLoading = true; this.gameId = this.route.snapshot.params.id; if(this.gameId) { const result = await this.getGame(); if(result) { await this.getTeams(); } } this.isLoading = false; public async getTeams() { const teamRef = this.teamsCollection.ref; const teamsByGameIdQuery = teamRef.where('gameId', '==', this.currentGame.docID).orderBy('score', 'desc'); onSnapshot(teamsByGameIdQuery, (snapshot) => { const teams: ITeam[] = []; snapshot.docs.forEach((doc) => { teams.push({ ...doc.data(), id: doc.id}); this.currentTeams = teams; }); console.log(this.currentTeams); }); }
相关截图:

根因分析
- 核心原因:Firestore的
onSnapshot回调默认运行在Angular的NgZone上下文外部,回调内修改组件成员变量currentTeams时,Angular的变更检测机制无法感知到数据变化,不会触发视图重渲染。 - 逻辑瑕疵1:
getTeams方法中this.currentTeams = teams的赋值写在了forEach循环内部,循环每执行一次就会触发一次赋值,第一次赋值时数组仅包含第一条数据,逻辑不规范。 - 逻辑瑕疵2:
onSnapshot是事件订阅式API,不会返回Promise,对其使用await没有实际效果,代码会在注册完监听后立刻执行this.isLoading = false,此时第一次快照数据还未返回。 - 模板语法瑕疵:
*ngFor中索引变量写法错误,let index未做赋值,模板中拿到的index始终为undefined。
修复方案
- 引入Angular的
NgZone服务,将onSnapshot回调内的数据更新逻辑包裹在ngZone.run()中执行,强制让Angular感知到数据变更,触发视图更新。 - 将
currentTeams的赋值操作移到forEach循环外部,等所有文档数据处理完成后再一次性赋值。 - 调整加载状态逻辑,等第一次快照数据返回并赋值完成后,再将
isLoading设为false。 - 修正模板中
*ngFor的索引变量写法。
修复后的类代码:
// 顶部引入NgZone import { NgZone } from '@angular/core'; // 构造函数注入NgZone constructor( private ngZone: NgZone, // 其余原有依赖保持不变 ) {} // 初始化逻辑修正 async ngOnInit() { this.isLoading = true; this.gameId = this.route.snapshot.params.id; if(this.gameId) { const result = await this.getGame(); if(result) { // 移除await,onSnapshot不是Promise无需等待 this.getTeams(); return; } } this.isLoading = false; } public getTeams() { const teamRef = this.teamsCollection.ref; const teamsByGameIdQuery = teamRef.where('gameId', '==', this.currentGame.docID).orderBy('score', 'desc'); onSnapshot(teamsByGameIdQuery, (snapshot) => { // 数据更新包裹在ngZone.run中,触发变更检测 this.ngZone.run(() => { const teams: ITeam[] = []; snapshot.docs.forEach((doc) => { teams.push({ ...doc.data() as ITeam, id: doc.id}); }); // 循环结束后统一赋值 this.currentTeams = teams; // 首次数据返回后关闭loading if(this.isLoading) { this.isLoading = false; } }); }); }
修复后的模板*ngFor行:
<ion-item *ngFor="let team of currentTeams; let i = index" [color]="team.color" style="margin: 10px;"> <p>{{i + 1}}</p> <!-- 其余模板内容保持不变 -->
内容的提问来源于stack exchange,提问作者Rhett Harrison
相关产品推荐
相关产品推荐

