Angular用provideAppInitializer初始化RxDB报错:Database not initialized yet
我是React开发者,现在尝试用Angular开发依赖RxDB的应用。我把RxDB封装在根服务DatabaseService中,在app.config.ts里通过provideAppInitializer调用服务的initDatabase方法初始化数据库,但最早访问数据的ItemsComponent调用getDatabase时抛出"Database not initialized yet"错误。相关代码如下:
相关代码
DatabaseService
import { Injectable } from '@angular/core'; import { createRxDatabase, RxDatabase } from 'rxdb'; import { ItemCollection, itemSchema } from '../items/items.schema'; import { getRxStorageMemory } from 'rxdb/plugins/storage-memory'; type MyDatabaseCollections = { items: ItemCollection; }; type MyDatabase = RxDatabase<MyDatabaseCollections>; @Injectable({ providedIn: 'root', }) export class DatabaseService { private static database: MyDatabase; constructor() {} async initDatabase(): Promise<void> { if (!DatabaseService.database) { const db = await createRxDatabase<MyDatabaseCollections>({ name: 'mydb', storage: getRxStorageMemory(), }); await db.addCollections({ items: { schema: itemSchema, }, }); DatabaseService.database = db; } } getDatabase(): MyDatabase { if (!DatabaseService.database) { throw new Error('Database not initialized yet'); } return DatabaseService.database; } }
app.config.ts
providers: [ DatabaseService, provideAppInitializer(() => inject(DatabaseService).initDatabase()), ],
ItemsComponent
@Component({ selector: 'app-items', imports: [ToastModule], templateUrl: './items.component.html', }) export class ItemsComponent { items: ItemType[] = []; constructor( private toastService: ToastService, private databaseService: DatabaseService, ) { this.getItems(); } getItems() { const db = this.databaseService.getDatabase(); const items = db.items.find().$; items.subscribe((docs) => { console.log(docs); }); } }
问题根源
Angular的provideAppInitializer虽支持异步初始化函数(返回Promise),但组件构造函数是同步执行的。如果数据库初始化的异步操作还未完成,组件就已经尝试获取数据库实例,就会触发该错误。即使依赖App Initializer等待初始化完成,某些场景下(比如路由预加载、组件提前实例化)仍可能出现组件先于数据库初始化完成的情况。
解决方案
方案1:让数据库服务提供异步获取实例的方法
修改DatabaseService,将getDatabase改为异步方法,确保组件只有在数据库初始化完成后才能获取实例:
import { Injectable } from '@angular/core'; import { createRxDatabase, RxDatabase } from 'rxdb'; import { ItemCollection, itemSchema } from '../items/items.schema'; import { getRxStorageMemory } from 'rxdb/plugins/storage-memory'; type MyDatabaseCollections = { items: ItemCollection; }; type MyDatabase = RxDatabase<MyDatabaseCollections>; @Injectable({ providedIn: 'root', }) export class DatabaseService { private static databasePromise: Promise<MyDatabase> | null = null; constructor() {} async initDatabase(): Promise<void> { if (!DatabaseService.databasePromise) { DatabaseService.databasePromise = (async () => { const db = await createRxDatabase<MyDatabaseCollections>({ name: 'mydb', storage: getRxStorageMemory(), }); await db.addCollections({ items: { schema: itemSchema, }, }); return db; })(); } await DatabaseService.databasePromise; } async getDatabase(): Promise<MyDatabase> { if (!DatabaseService.databasePromise) { throw new Error('Database initialization has not been started'); } return DatabaseService.databasePromise; } }
修改ItemsComponent,在ngOnInit中异步获取数据库(避免在构造函数中执行异步操作):
import { Component, OnInit } from '@angular/core'; // 导入其他依赖... @Component({ selector: 'app-items', imports: [ToastModule], templateUrl: './items.component.html', }) export class ItemsComponent implements OnInit { items: ItemType[] = []; constructor( private toastService: ToastService, private databaseService: DatabaseService, ) {} ngOnInit(): void { this.getItems(); } async getItems() { try { const db = await this.databaseService.getDatabase(); const items$ = db.items.find().$; items$.subscribe((docs) => { console.log(docs); this.items = docs; }); } catch (err) { this.toastService.show('Failed to load items', { status: 'error' }); } } }
方案2:用Observable跟踪数据库就绪状态
通过BehaviorSubject监听数据库初始化状态,组件订阅该状态后再执行数据操作:
修改DatabaseService:
import { Injectable, BehaviorSubject, Observable, filter } from '@angular/core'; import { createRxDatabase, RxDatabase } from 'rxdb'; import { ItemCollection, itemSchema } from '../items/items.schema'; import { getRxStorageMemory } from 'rxdb/plugins/storage-memory'; type MyDatabaseCollections = { items: ItemCollection; }; type MyDatabase = RxDatabase<MyDatabaseCollections>; @Injectable({ providedIn: 'root', }) export class DatabaseService { private databaseSubject = new BehaviorSubject<MyDatabase | null>(null); database$: Observable<MyDatabase> = this.databaseSubject.asObservable().pipe( filter(db => db !== null) ) as Observable<MyDatabase>; constructor() {} async initDatabase(): Promise<void> { if (!this.databaseSubject.value) { const db = await createRxDatabase<MyDatabaseCollections>({ name: 'mydb', storage: getRxStorageMemory(), }); await db.addCollections({ items: { schema: itemSchema, }, }); this.databaseSubject.next(db); } } }
修改ItemsComponent:
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs'; // 导入其他依赖... @Component({ selector: 'app-items', imports: [ToastModule], templateUrl: './items.component.html', }) export class ItemsComponent implements OnInit, OnDestroy { items: ItemType[] = []; private subscriptions = new Subscription(); constructor( private toastService: ToastService, private databaseService: DatabaseService, ) {} ngOnInit(): void { const dbSubscription = this.databaseService.database$.subscribe(db => { const itemsSubscription = db.items.find().$.subscribe((docs) => { console.log(docs); this.items = docs; }); this.subscriptions.add(itemsSubscription); }); this.subscriptions.add(dbSubscription); } ngOnDestroy(): void { this.subscriptions.unsubscribe(); } }
额外检查
确保app.config.ts中的provideAppInitializer正确返回Promise,Angular会等待该Promise完成后再引导应用:
providers: [ DatabaseService, provideAppInitializer(async () => { const dbService = inject(DatabaseService); await dbService.initDatabase(); }) ],
内容的提问来源于stack exchange,提问作者Kadiem Alqazzaz

