From d997c1ce980ff43074353d0dd1de578f1f47acfc Mon Sep 17 00:00:00 2001 From: markostanimirovic Date: Wed, 17 Aug 2022 00:26:23 +0200 Subject: [PATCH] fix(component-store): move isInitialized check to queueScheduler context on state update Closes #2991 --- .../spec/component-store.spec.ts | 15 ++++++++- .../component-store/src/component-store.ts | 32 +++++++++---------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/modules/component-store/spec/component-store.spec.ts b/modules/component-store/spec/component-store.spec.ts index 457dbe3af0..4911385154 100644 --- a/modules/component-store/spec/component-store.spec.ts +++ b/modules/component-store/spec/component-store.spec.ts @@ -203,7 +203,7 @@ describe('Component Store', () => { ); it( - 'does not throws an Error when updater is called with async Observable' + + 'does not throw an Error when updater is called with async Observable' + ' before initialization, that emits the value after initialization', marbles((m) => { const componentStore = new ComponentStore(); @@ -236,6 +236,19 @@ describe('Component Store', () => { ); }) ); + + it( + 'does not throw an Error when ComponentStore initialization and' + + ' state update are scheduled via queueScheduler', + () => { + expect(() => { + queueScheduler.schedule(() => { + const componentStore = new ComponentStore({ foo: false }); + componentStore.patchState({ foo: true }); + }); + }).not.toThrow(); + } + ); }); describe('updates the state', () => { diff --git a/modules/component-store/src/component-store.ts b/modules/component-store/src/component-store.ts index 0043b4ac2b..40215c8f28 100644 --- a/modules/component-store/src/component-store.ts +++ b/modules/component-store/src/component-store.ts @@ -13,7 +13,6 @@ import { EMPTY, } from 'rxjs'; import { - concatMap, takeUntil, withLatestFrom, map, @@ -22,6 +21,7 @@ import { take, tap, catchError, + observeOn, } from 'rxjs/operators'; import { debounceSync } from './debounce-sync'; import { @@ -61,9 +61,6 @@ export class ComponentStore implements OnDestroy { private readonly stateSubject$ = new ReplaySubject(1); private isInitialized = false; - private notInitializedErrorMessage = - `${this.constructor.name} has not been initialized yet. ` + - `Please make sure it is initialized before updating/getting.`; // Needs to be after destroy$ is declared because it's used in select. readonly state$: Observable = this.select((s) => s); private ɵhasProvider = false; @@ -125,15 +122,11 @@ export class ComponentStore implements OnDestroy { : of(observableOrValue); const subscription = observable$ .pipe( - concatMap((value) => - this.isInitialized - ? // Push the value into queueScheduler - scheduled([value], queueScheduler).pipe( - withLatestFrom(this.stateSubject$) - ) - : // If state was not initialized, we'll throw an error. - throwError(() => new Error(this.notInitializedErrorMessage)) - ), + // Push the value into queueScheduler + observeOn(queueScheduler), + // If the state is not initialized yet, we'll throw an error. + tap(() => this.assertStateIsInitialized()), + withLatestFrom(this.stateSubject$), // eslint-disable-next-line @typescript-eslint/no-non-null-assertion map(([value, currentState]) => updaterFn(currentState, value!)), tap((newState) => this.stateSubject$.next(newState)), @@ -209,9 +202,7 @@ export class ComponentStore implements OnDestroy { protected get(): T; protected get(projector: (s: T) => R): R; protected get(projector?: (s: T) => R): R | T { - if (!this.isInitialized) { - throw new Error(this.notInitializedErrorMessage); - } + this.assertStateIsInitialized(); let value: R | T; this.stateSubject$.pipe(take(1)).subscribe((state) => { @@ -353,6 +344,15 @@ export class ComponentStore implements OnDestroy { } }); } + + private assertStateIsInitialized(): void { + if (!this.isInitialized) { + throw new Error( + `${this.constructor.name} has not been initialized yet. ` + + `Please make sure it is initialized before updating/getting.` + ); + } + } } function processSelectorArgs<