如何使用Angular/Fire模块化API实现通用转换器?
问题背景
从旧版@angular/fire compat API迁移至新模块化API时,原通用数据获取方法无法直接适配,编写的通用转换器出现类型不兼容错误,核心是QueryDocumentSnapshot类型的参数定义问题。
原Compat API代码
通用数据获取方法
get<T extends DbModel>(TCreator: new () => T, id: string, refParams: any = undefined): Observable<T | undefined> { let o = new TCreator(); return this._afs.doc<T>(`${o.getRef(refParams)}/${id}`) .valueChanges().pipe( map(value => { return value ? { ...value, id } : value; }) ); }
DbModel抽象类
export abstract class DbModel { [index: string]: any; abstract getRef(refParams: any): string; getGroupName(): string { throw new Error('Group name not set'); } id!: string; }
模块化API中的错误实现
错误的通用转换器
private _converter = <T>() => ({ toFirestore: (data: PartialWithFieldValue<T>) => data, fromFirestore: (snap: QueryDocumentSnapshot<T>) => snap.data() })
出错的getModular方法
getModular<T extends DbModel>(TCreator: new () => T, id: string, refParams: any = undefined) : Observable<T | undefined> { let instance = new TCreator(); let docRef = doc(this._afsModular, `${instance.getRef(refParams)}/${id}`) .withConverter(this._converter<T>()); }
错误信息
Overload 2 of 2, '(converter: FirestoreDataConverter
): DocumentReference ', gave the following error.
Argument of type '{ toFirestore: (data: PartialWithFieldValue) => PartialWithFieldValue ; fromFirestore: (snap: QueryDocumentSnapshot ) => T; }' is not assignable to parameter of type 'FirestoreDataConverter '.
Types of property 'fromFirestore' are incompatible.
Type '(snap: QueryDocumentSnapshot) => T' is not assignable to type '(snapshot: QueryDocumentSnapshot, options?: SnapshotOptions | undefined) => T'.
Types of parameters 'snap' and 'snapshot' are incompatible.
Property 'isEqual' is missing in type 'QueryDocumentSnapshot' but required in type 'QueryDocumentSnapshot'.ts(2769)
index.d.ts(9611, 5): 'isEqual' is declared here.
解决方案
修正后的通用转换器
import { FirestoreDataConverter, PartialWithFieldValue, QueryDocumentSnapshot, SnapshotOptions } from '@angular/fire/firestore'; private _converter = <T extends DbModel>() => ({ toFirestore: (data: PartialWithFieldValue<T>) => { // 移除id字段,避免将文档ID存入Firestore数据中 const { id, ...rest } = data; return rest; }, fromFirestore: (snapshot: QueryDocumentSnapshot, options?: SnapshotOptions) => { // 获取文档数据并合并ID,与原Compat API逻辑对齐 const data = snapshot.data(options) as Omit<T, 'id'>; return { ...data, id: snapshot.id } as T; } } as FirestoreDataConverter<T>);
修正后的getModular方法
import { doc, docData } from '@angular/fire/firestore'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; getModular<T extends DbModel>(TCreator: new () => T, id: string, refParams: any = undefined): Observable<T | undefined> { const instance = new TCreator(); const docRef = doc(this._afsModular, `${instance.getRef(refParams)}/${id}`).withConverter(this._converter<T>()); // 使用docData获取数据,替代原compat API的valueChanges return docData(docRef).pipe( map(doc => doc ?? undefined) ); }
关键说明
- 参数类型修正:模块化API中
fromFirestore的参数是QueryDocumentSnapshot(无泛型)和可选的SnapshotOptions,而非带泛型的QueryDocumentSnapshot<T>,这是导致类型不兼容的核心原因。 - ID合并逻辑:原compat API会自动将文档ID合并到返回值中,模块化API需要手动在
fromFirestore方法中把snapshot.id合并到数据对象里。 - 数据清理:
toFirestore方法中移除id字段,避免将文档ID冗余存储到Firestore文档数据内。
内容的提问来源于stack exchange,提问作者Nicolas Gorga

