diff --git a/libs/state/spec/rx-state.service.spec.ts b/libs/state/spec/rx-state.service.spec.ts index 030e4a9ed7..2edc53a44f 100644 --- a/libs/state/spec/rx-state.service.spec.ts +++ b/libs/state/spec/rx-state.service.spec.ts @@ -1,3 +1,4 @@ +import { ErrorHandler, Injector } from '@angular/core'; // tslint:disable-next-line:nx-enforce-module-boundaries import { jestMatcher } from '@test-helpers'; import { fakeAsync, TestBed } from '@angular/core/testing'; @@ -10,7 +11,10 @@ import { TestScheduler } from 'rxjs/testing'; // tslint:disable-next-line:nx-enforce-module-boundaries import { RxState, select } from '@rx-angular/state'; import { map, pluck, switchMap, take, takeUntil } from 'rxjs/operators'; -import { asyncScheduler, from, interval, of, scheduled, Subject } from 'rxjs'; +import { from, interval, of, Subject, throwError, asyncScheduler, from, interval, scheduled, Subject } from 'rxjs'; +import { + ɵsetCurrentInjector as setCurrentInjector +} from '@angular/core'; function setupState(cfg: { initialState?: T }) { const { initialState } = { ...cfg }; @@ -587,5 +591,18 @@ describe('RxStateService', () => { state.hold(of(1, 2, 3), effect); expect(calls).toBe(3); })); + + it('should invoke provided ErrorHandler', fakeAsync(() => { + const state = setupState({ initialState: initialPrimitiveState }); + const customErrorHandler: ErrorHandler = { + handleError: jest.fn() + }; + TestBed.overrideProvider(ErrorHandler, { useValue: customErrorHandler }); + setCurrentInjector(TestBed.inject(Injector)); + state.hold(throwError(new Error('something went wrong'))); + expect(customErrorHandler.handleError).toHaveBeenCalledWith( + new Error('something went wrong') + ); + })); }); }); diff --git a/libs/state/src/lib/rx-state.service.ts b/libs/state/src/lib/rx-state.service.ts index 6ae9b744c5..78e468a743 100644 --- a/libs/state/src/lib/rx-state.service.ts +++ b/libs/state/src/lib/rx-state.service.ts @@ -1,8 +1,32 @@ -import { Injectable, OnDestroy } from '@angular/core'; -import { EMPTY, isObservable, Observable, OperatorFunction, Subscribable, Subscription, Unsubscribable } from 'rxjs'; +import { + ErrorHandler, + inject, + Injectable, + InjectFlags, + OnDestroy, +} from '@angular/core'; +import { + EMPTY, + isObservable, + Observable, + OperatorFunction, + Subscribable, + Subscription, + Unsubscribable, +} from 'rxjs'; import { catchError, map, pluck, tap } from 'rxjs/operators'; -import { isKeyOf, isOperateFnArrayGuard, isStringArrayGuard, pipeFromArray, safePluck } from './core'; -import { AccumulationFn, createAccumulationObservable, createSideEffectObservable } from './cdk'; +import { + isKeyOf, + isOperateFnArrayGuard, + isStringArrayGuard, + pipeFromArray, + safePluck, +} from './core'; +import { + AccumulationFn, + createAccumulationObservable, + createSideEffectObservable, +} from './cdk'; import { stateful } from './rxjs/operators'; type ProjectStateFn = (oldState: T) => Partial; @@ -120,30 +144,38 @@ export class RxState implements OnDestroy, Subscribable { k3: K3 ): T[K1][K2][K3]; /** @internal **/ - get(k1: K1, k2: K2, k3: K3, k4: K4): T[K1][K2][K3][K4]; + K4 extends keyof T[K1][K2][K3] + >(k1: K1, k2: K2, k3: K3, k4: K4): T[K1][K2][K3][K4]; /** @internal **/ - get(k1: K1, k2: K2, k3: K3, k4: K4, k5: K5): T[K1][K2][K3][K4][K5]; + K5 extends keyof T[K1][K2][K3][K4] + >(k1: K1, k2: K2, k3: K3, k4: K4, k5: K5): T[K1][K2][K3][K4][K5]; /** @internal **/ - get(k1: K1, k2: K2, k3: K3, k4: K4, k5: K5, k6: K6): T[K1][K2][K3][K4][K5][K6]; + K6 extends keyof T[K1][K2][K3][K4][K5] + >(k1: K1, k2: K2, k3: K3, k4: K4, k5: K5, k6: K6): T[K1][K2][K3][K4][K5][K6]; /** @internal **/ - get( + K6 extends keyof T[K1][K2][K3][K4][K5] + >( ...keys: | [K1] | [K1, K2] @@ -163,9 +195,9 @@ export class RxState implements OnDestroy, Subscribable { if (!!keys && keys.length) { return safePluck(this.accumulator.state, keys); } else { - return hasStateAnyKeys ? - this.accumulator.state : - undefined as unknown as T; + return hasStateAnyKeys + ? this.accumulator.state + : ((undefined as unknown) as T); } } @@ -472,33 +504,41 @@ export class RxState implements OnDestroy, Subscribable { /** * @internal */ - select(k1: K1, k2: K2, k3: K3): Observable; + K3 extends keyof T[K1][K2] + >(k1: K1, k2: K2, k3: K3): Observable; /** * @internal */ - select(k1: K1, k2: K2, k3: K3, k4: K4): Observable; + K4 extends keyof T[K1][K2][K3] + >(k1: K1, k2: K2, k3: K3, k4: K4): Observable; /** * @internal */ - select(k1: K1, k2: K2, k3: K3, k4: K4, k5: K5): Observable; + K5 extends keyof T[K1][K2][K3][K4] + >(k1: K1, k2: K2, k3: K3, k4: K4, k5: K5): Observable; /** * @internal */ - select( + K6 extends keyof T[K1][K2][K3][K4][K5] + >( k1: K1, k2: K2, k3: K3, @@ -547,7 +587,14 @@ export class RxState implements OnDestroy, Subscribable { obsOrObsWithSideEffect: Observable, sideEffectFn?: (arg: S) => void ): void { - const sideEffect = obsOrObsWithSideEffect.pipe(catchError(e => EMPTY)) + const sideEffect = obsOrObsWithSideEffect.pipe( + catchError((e) => { + // used injector for compatibility with https://github.com/rx-angular/rx-angular/blob/master/libs/state/docs/usage.md#inherit + const errorHandler = inject(ErrorHandler, InjectFlags.Optional); + errorHandler?.handleError(e); + return EMPTY; + }) + ); if (typeof sideEffectFn === 'function') { this.effectObservable.nextEffectObservable( sideEffect.pipe(tap(sideEffectFn))