You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

页面加载时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能否处理数组新增值?

不能,原因有两点:

  1. formArray不是@Input()装饰的输入属性,ngOnChanges只会监听组件输入属性的变化,所以当前的ngOnChanges逻辑完全不会触发。
  2. 即使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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 08:23:55