页面加载时Firebase数据控制台显示但HTML不渲染的问题排查
问题描述
我从Firebase文档中获取数组数据,控制台能打印出数据,但前端页面就是不显示。已经用了async/await,标识加载完成的doneLoading也返回true,可数据还是渲染不到HTML里。另外想问,如果数组新增了值,当前的ngOnChanges函数能正确处理这个变化吗?
.ts 文件代码
async ngOnInit() { this.adminCode = this.route.snapshot.params["adminCode"]; await this.listForms(this.adminCode); this.doneLoading = this.checkArr(); console.log(this.formArray) console.log(this.doneLoading + 'doneLoading?') } ngAfterViewInit(): void { } ngOnChanges(changes: SimpleChanges) { const mainDataChange = changes['formArray'] ; if (mainDataChange) { this.formArray = mainDataChange.currentValue; //this.sortedData = this.dataArray; } } checkArr(){ debugger if(this.formArray?.length > 0){ return true } else{ return false } } async listForms(adminCode: string){ this.doneLoading = false; const forms = await this.fireCustomAssessment.readAdminAssessmentList(adminCode) .get() .toPromise() .then((doc) => { this.data = doc.data(); this.formArray = this.data?.formArray; console.log(this.formArray) }) this.doneLoading = true; return forms; }
.html 文件代码
<section> <div class="table-responsive"> <table class="table"> <thead> <tr> <th>Form name</th> </tr> </thead> <tbody> <div *ngIf="doneLoading == false">Loading... If data doesn't show, then you have no data yet.</div> <div *ngIf="doneLoading == true"> <tr *ngFor="let formName of formArray"> <td> {{formName}} </td> <td> <button class="button" mat-raised-button (click)="goToForm(formName)">Take Assessment</button> </td> </tr> </div> </tbody> </table> </div> </section>
解决方案
一、数据无法渲染的问题
1. HTML结构错误是核心原因
<div>不能直接嵌套在<tbody>标签内,浏览器会自动将非法的<div>移到表格外部,导致*ngFor生成的<tr>不在表格的有效区域内,自然无法渲染。
修改方案:用<ng-container>替代<div>(它不会生成实际DOM元素,仅作为逻辑容器),或者直接把*ngIf绑定到<tr>上:
<tbody> <ng-container *ngIf="doneLoading == false"> <tr> <td colspan="2">Loading... If data doesn't show, then you have no data yet.</td> </tr> </ng-container> <ng-container *ngIf="doneLoading == true"> <tr *ngFor="let formName of formArray"> <td>{{formName}}</td> <td> <button class="button" mat-raised-button (click)="goToForm(formName)">Take Assessment</button> </td> </tr> </ng-container> </tbody>
2. 修正doneLoading的赋值逻辑
当前listForms函数最后直接将doneLoading设为true,忽略了formArray为空的情况,应该调用checkArr()来判断:
async listForms(adminCode: string){ this.doneLoading = false; await this.fireCustomAssessment.readAdminAssessmentList(adminCode) .get() .toPromise() .then((doc) => { this.data = doc.data(); // 生成新数组触发变更检测 this.formArray = [...(this.data?.formArray || [])]; console.log(this.formArray) }) // 用checkArr判断加载状态 this.doneLoading = this.checkArr(); }
3. 确保Angular能追踪数组变化
Firebase返回的数组可能不是Angular默认能追踪的类型,通过[...arr]生成新数组,或者注入ChangeDetectorRef手动触发变更检测(二选一即可):
// 注入ChangeDetectorRef constructor(private cdr: ChangeDetectorRef) {} async listForms(adminCode: string){ this.doneLoading = false; await this.fireCustomAssessment.readAdminAssessmentList(adminCode) .get() .toPromise() .then((doc) => { this.data = doc.data(); this.formArray = this.data?.formArray; // 手动触发变更检测 this.cdr.detectChanges(); console.log(this.formArray) }) this.doneLoading = this.checkArr(); }
二、ngOnChanges能否处理数组新增值?
不能,原因有两点:
formArray不是@Input()装饰的输入属性,ngOnChanges只会监听组件输入属性的变化,所以当前的ngOnChanges逻辑完全不会触发。- 即使
formArray是输入属性,ngOnChanges也只会在数组引用发生变化时触发(比如重新赋值一个新数组),如果只是在原数组里新增/删除元素,数组引用没变,ngOnChanges不会响应。
正确处理数组内部变化的方案
使用BehaviorSubject封装数组,通过订阅响应变化:
import { BehaviorSubject } from 'rxjs'; // 组件内定义 private formArraySubject = new BehaviorSubject<any[]>([]); // 暴露给模板的可观察对象 formArray$ = this.formArraySubject.asObservable(); // 获取数据时更新 async listForms(adminCode: string){ this.doneLoading = false; await this.fireCustomAssessment.readAdminAssessmentList(adminCode) .get() .toPromise() .then((doc) => { this.data = doc.data(); this.formArraySubject.next(this.data?.formArray || []); console.log(this.formArraySubject.value) }) this.doneLoading = this.checkArr(); } // 新增元素时 addFormItem(newItem: any) { const currentArr = this.formArraySubject.value; this.formArraySubject.next([...currentArr, newItem]); }
模板中用async管道订阅:
<tr *ngFor="let formName of formArray$ | async"> <td>{{formName}}</td> <td> <button class="button" mat-raised-button (click)="goToForm(formName)">Take Assessment</button> </td> </tr>
内容的提问来源于stack exchange,提问作者TSH
相关产品推荐
相关产品推荐

