Angular 15迁移Firestore后组件首次加载不渲染问题求助
我使用Angular 15.2.0开发相册项目,此前基于Angular的InMemoryDBService测试完全正常。用户选择筛选条件后,画廊服务会查询匹配的照片并返回Observable,组件订阅后将photos属性赋值为返回结果。
模板代码
<app-filter-select *ngIf="!filtersChosen && filterList.length !== 0" [filters]="filterList" (Submitted)="setGallery($event)" ></app-filter-select> <div *ngIf="filtersChosen"> <div class="center-screen" *ngIf="loading"> <mat-spinner class="center-screen"></mat-spinner> </div> <mat-grid-list *ngIf="photos && photos.length > 0" class="gallery-container" gutterSize="1em" cols="3" > <mat-grid-tile class="photo-holder" *ngFor="let photo of photos" [colspan]="1" [rowspan]="1" > <a [routerLink]="['/photos', photo.id]"> <app-gallerycard (loaded)="onLoad()" [hidden]=loading [photo]="photo" style="display: flex; justify-content: center" ></app-gallerycard> </a> </mat-grid-tile> </mat-grid-list> </div>
网格瓦片的[hidden]属性绑定到loading属性,所有照片加载完成后该属性会变为false。
问题现象
迁移到Firestore生产环境后出现异常:提交筛选条件后,subscribe回调中为组件photos属性赋值,但<mat-grid-list>完全不渲染,loading属性始终不变,加载spinner持续显示,照片无法展示。仅首次导航到画廊页面时会出现此问题,再次点击画廊导航按钮或切换页面返回后,照片能正常渲染。我未修改画廊组件,仅改动了画廊服务的getPhotosWithFilterArray()方法,将查询源从InMemoryDB改为Firestore。
组件筛选提交逻辑
photos?: Photo[]; setGallery(data: any) { this.filtersChosen = true; const filterNames: string[] = []; data.filters.forEach((f: Genre) => { filterNames.push(f.name.toUpperCase()); }); this.galleryService .getPhotosWithFilterArray(filterNames) .subscribe((photos) => { this.photos = photos; }); }
画廊服务实现
getPhotosWithFilterArray(filters: Array<string>): Observable<Photo[]> { const photosRef = collection(this.firestore, 'photos'); const filteredPhotos = query(photosRef, where('genre', 'in', filters)); return collectionData(filteredPhotos).pipe( tap((p) => console.log(p as Photo[])), map((p) => p as Photo[]), catchError(this.handleError<Photo[]>('getPhotosWithFilterArray', [])) ); }
已尝试的解决方法
- 将
<mat-grid-list>的*ngIf改为绑定photosFetched属性,并在subscribe回调中将其设为true - 实现
ngDoCheck(),在photos值变化时将photosFetched设为true - 实现
ngOnChanges(),在捕获到变更时将photosFetched设为true
尚未尝试使用async管道渲染Observable数据,因为组件其他部分依赖photos为数组而非Observable;也考虑过将<app-filter-select>组件抽离到其他页面,但InMemoryDB环境下该逻辑正常,暂不采用此方案。
再次强调:仅首次设置筛选条件时出现问题,迁移到Firestore前无此异常。photos已绑定有效数组,photosFetched值也已更新,但均无法触发模板的状态变更。
更新:临时解决方案
未能彻底解决问题,推测可能是Angular变更检测的Bug。找到一个临时可行的方案:
画廊服务改为从Firestore获取整个文档集合,不再返回Observable流,而是通过getPhotos()函数设置私有_photos数组。网站加载图片前,通过以下方法检查数组是否初始化:
photosInitializedYet() { return this._photos.length !== 0 }
由于Firestore查询是异步操作,会等待getPhotos()的响应,*ngFor元素会等到带有文件路径的Photo对象存在后再渲染。此方案需在服务中保存Firestore数据库的静态副本,虽不优雅但能正常工作。
内容的提问来源于stack exchange,提问作者mFsGiant

