NgRx Component Store调用patch报错未初始化,如何判断就绪?
我正在使用NgRx Component Store 14.3.0,通过如下方式继承ComponentStore实现抽象基类MyStoreBase:
@Injectable() export abstract class MyStoreBase<T extends MyStateBase> extends ComponentStore<T> implements OnDestroy {
有时在所属组件刚创建后,调用组件存储方法时会出现**"MyStore has not been initialized yet"**错误。我的初始化代码如下,同时使用NgRx全局Store获取配置后调用组件存储的getData方法:
constructor( private store$: Store<State>) { super(INITIAL_BASE_STATE as T); // 曾因调用patch时存储未初始化报错,故将订阅设为异步 setTimeout(() => this.initialise(), 100); } public initialise(): void { // 订阅更新 this.subs.sink = this.store$.select(fromApp.getConfig).pipe(filter(x => !!x)).subscribe(config => { this.getData(''); }); private getData = this.effect((trigger$: Observable<string>) => trigger$.pipe( tap(() => { this.patchState((state) => ({ ...state, isRequesting: true })); // ...其他逻辑 }),
问题出在tap中的patchState调用,使用setTimeout规避但无效。查看源码发现isInitialized通过异步调用设置为true,尝试订阅stateSubject但不可用,想知道如何判断组件存储已就绪以安全调用方法?
解决方案:利用ComponentStore的state$判断就绪状态
ComponentStore的state$是公开可访问的Observable,它会在存储初始化完成后发出初始状态,以此作为就绪判断的可靠依据,替代不可靠的setTimeout。
方案1:在getData effect中加入就绪判断
在effect逻辑里先等待state$发出第一个值,确认存储初始化完成后再执行patchState:
private getData = this.effect((trigger$: Observable<string>) => trigger$.pipe( // 等待存储初始化完成,取首个状态值确认就绪 withLatestFrom(this.state$.pipe(take(1))), tap(([_, state]) => { this.patchState({ ...state, isRequesting: true }); // ...其他业务逻辑 }) ));
方案2:在initialise中等待就绪后再订阅全局Store
调整初始化流程,确保订阅全局Store的操作在存储就绪后执行:
public initialise(): void { // 先等待ComponentStore初始化完成 this.state$.pipe(take(1)).subscribe(() => { this.subs.sink = this.store$.select(fromApp.getConfig).pipe(filter(x => !!x)).subscribe(config => { this.getData(''); }); }); }
同时可以移除constructor中的setTimeout,因为state$.pipe(take(1))已经能精准捕获初始化完成的时机。
方案3:结合组件ngOnInit生命周期(Store与组件绑定场景)
如果你的MyStoreBase是在组件的providers中声明的,可以将初始化逻辑移到ngOnInit中。ComponentStore会在组件ngOnInit阶段完成初始化,此时调用方法不会触发未初始化报错:
export abstract class MyStoreBase<T extends MyStateBase> extends ComponentStore<T> implements OnDestroy, OnInit { // ...其他代码 ngOnInit(): void { this.initialise(); } constructor(private store$: Store<State>) { super(INITIAL_BASE_STATE as T); // 移除原有的setTimeout } }
原理说明
ComponentStore的isInitialized是内部私有属性无法直接访问,但state$会在isInitialized被设为true后立即发出初始状态。通过take(1)获取这个状态值,就能准确判断存储已经就绪,从根源上避免时序问题,比依赖固定延迟的setTimeout更可靠。
内容的提问来源于stack exchange,提问作者peterc

