Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 246f7e6

Browse files
committed
fix(state): handle error emission in connect using ErrorHandler
1 parent eb6127a commit 246f7e6

File tree

4 files changed

+80
-39
lines changed

4 files changed

+80
-39
lines changed

libs/state/selections/src/lib/accumulation-observable.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,12 @@ const defaultAccumulator: AccumulationFn = <T>(st: T, sl: Partial<T>): T => {
2525
return { ...st, ...sl };
2626
};
2727

28-
export function createAccumulationObservable<T extends object>(
28+
export function createAccumulationObservable<T extends object>({
2929
stateObservables = new Subject<Observable<Partial<T>>>(),
3030
stateSlices = new Subject<Partial<T>>(),
31-
accumulatorObservable = new BehaviorSubject(defaultAccumulator)
32-
): Accumulator<T> {
31+
accumulatorObservable = new BehaviorSubject(defaultAccumulator),
32+
handleError = (e: any) => console.error(e),
33+
} = {}): Accumulator<T> {
3334
const signal$ = merge(
3435
stateObservables.pipe(
3536
distinctUntilChanged(),
@@ -45,7 +46,7 @@ export function createAccumulationObservable<T extends object>(
4546
),
4647
tap(
4748
(newState) => (compositionObservable.state = newState),
48-
(error) => console.error(error)
49+
(error) => handleError(error)
4950
),
5051
// @Notice We catch the error here as it get lost in between `publish` and `publishReplay`. We return empty to
5152
catchError((e) => EMPTY),

libs/state/spec/rx-state.component.spec.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1+
import {
2+
Component,
3+
ErrorHandler,
4+
Input,
5+
Output,
6+
ViewChild,
7+
} from '@angular/core';
18
import { ComponentFixture, TestBed } from '@angular/core/testing';
2-
import { Component, Input, Output, ViewChild } from '@angular/core';
3-
import { PrimitiveState } from '@test-helpers';
4-
import { createStateChecker } from './fixtures';
5-
import { Observable, Subject } from 'rxjs';
69
import { RxState } from '@rx-angular/state';
710
import { select } from '@rx-angular/state/selections';
11+
import { PrimitiveState } from '@test-helpers';
12+
import { Observable, Subject, throwError } from 'rxjs';
13+
import { createStateChecker } from './fixtures';
814

915
const initialChildState = { str: 'initialChildState' };
1016

@@ -100,33 +106,51 @@ export class RxStateGlueContainerComponent extends RxState<PrimitiveState> {
100106
describe('LocalProviderTestComponent', () => {
101107
let component: RxStateInjectionComponent;
102108
let fixture: ComponentFixture<RxStateInjectionComponent>;
109+
let errorHandlerSpy: any;
103110

104111
beforeEach(() => {
105-
TestBed.configureTestingModule({
112+
const testBed = TestBed.configureTestingModule({
106113
declarations: [RxStateInjectionComponent],
114+
providers: [
115+
{ provide: ErrorHandler, useValue: { handleError: jest.fn() } },
116+
],
107117
teardown: { destroyAfterEach: true },
108118
});
109119
fixture = TestBed.createComponent(RxStateInjectionComponent);
110120
component = fixture.componentInstance;
121+
errorHandlerSpy = testBed.inject(ErrorHandler).handleError;
111122
fixture.detectChanges();
112123
});
113124

114125
it('should create', () => {
115126
stateChecker.checkSubscriptions(component.state, 1);
116127
});
128+
129+
describe('state.connect', () => {
130+
it('should handle error through global ErrorHandler', () => {
131+
const error$ = throwError(() => new Error('whoops'));
132+
component.state.connect(error$);
133+
expect(errorHandlerSpy).toHaveBeenCalledWith(new Error('whoops'));
134+
});
135+
});
117136
});
118137

119138
describe('InheritanceTestComponent', () => {
120139
let component: RxStateInheritanceComponent;
121140
let fixture: ComponentFixture<RxStateInheritanceComponent>;
141+
let errorHandlerSpy: any;
122142

123143
beforeEach(() => {
124-
TestBed.configureTestingModule({
144+
const testBed = TestBed.configureTestingModule({
125145
declarations: [RxStateInheritanceComponent],
126146
teardown: { destroyAfterEach: true },
147+
providers: [
148+
{ provide: ErrorHandler, useValue: { handleError: jest.fn() } },
149+
],
127150
});
128151
fixture = TestBed.createComponent(RxStateInheritanceComponent);
129152
component = fixture.componentInstance;
153+
errorHandlerSpy = testBed.inject(ErrorHandler).handleError;
130154
fixture.detectChanges();
131155
});
132156

@@ -135,4 +159,12 @@ describe('InheritanceTestComponent', () => {
135159
component.ngOnDestroy();
136160
stateChecker.checkSubscriptions(component, 0);
137161
});
162+
163+
describe('state.connect', () => {
164+
it('should handle error through global ErrorHandler', () => {
165+
const error$ = throwError(() => new Error('whoops'));
166+
component.connect(error$);
167+
expect(errorHandlerSpy).toHaveBeenCalledWith(new Error('whoops'));
168+
});
169+
});
138170
});

libs/state/spec/rx-state.service.spec.ts

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ import { TestScheduler } from 'rxjs/testing';
1313
import { createStateChecker } from './fixtures';
1414

1515
function setupState<T extends object>(cfg: { initialState?: T }) {
16+
const testBed = TestBed.configureTestingModule({
17+
providers: [RxState],
18+
});
19+
const state = testBed.inject(RxState<T>);
1620
const { initialState } = { ...cfg };
17-
const state = new RxState<T>();
1821
if (initialState) {
1922
state.set(initialState);
2023
}
@@ -36,56 +39,56 @@ beforeEach(() => {
3639
});
3740

3841
describe('RxStateService', () => {
39-
let service: RxState<PrimitiveState>;
40-
41-
beforeEach(() => {
42-
TestBed.configureTestingModule({
43-
teardown: { destroyAfterEach: true },
44-
});
45-
service = setupState({});
46-
});
42+
let state: RxState<PrimitiveState>;
4743

4844
it('should be created', () => {
49-
expect(service).toBeTruthy();
45+
state = setupState({});
46+
expect(state).toBeTruthy();
5047
});
5148

5249
it('should be hot on instantiation', () => {
53-
stateChecker.checkSubscriptions(service, 1);
50+
state = setupState({});
51+
stateChecker.checkSubscriptions(state, 1);
5452
});
5553

5654
it('should unsubscribe on ngOnDestroy call', () => {
57-
stateChecker.checkSubscriptions(service, 1);
58-
service.ngOnDestroy();
59-
stateChecker.checkSubscriptions(service, 0);
55+
state = setupState({});
56+
57+
stateChecker.checkSubscriptions(state, 1);
58+
state.ngOnDestroy();
59+
stateChecker.checkSubscriptions(state, 0);
6060
});
6161

6262
describe('State', () => {
63+
beforeEach(() => {
64+
state = setupState({});
65+
});
66+
6367
it('should create new instance', () => {
64-
const state = new RxState<PrimitiveState>();
65-
expect(state).toBeDefined();
68+
expect(state).toBeInstanceOf(RxState);
6669
});
6770
});
6871

6972
describe('$', () => {
73+
beforeEach(() => {
74+
state = setupState({});
75+
});
76+
7077
it('should return NO empty state after init when subscribing late', () => {
7178
testScheduler.run(({ expectObservable }) => {
72-
const state = setupState({});
7379
expectObservable(state.$).toBe('');
7480
});
7581
});
7682

7783
it('should return No changes when subscribing late', () => {
7884
testScheduler.run(({ expectObservable }) => {
79-
const state = new RxState<PrimitiveState>();
8085
state.subscribe();
81-
8286
state.set({ num: 42 });
8387
expectObservable(state.$.pipe(pluck('num'))).toBe('');
8488
});
8589
});
8690

8791
it('should return new changes', () => {
88-
const state = new RxState<PrimitiveState>();
8992
state.subscribe();
9093
state.set({ num: 42 });
9194
const slice$ = state.$.pipe(select('num'));
@@ -97,26 +100,26 @@ describe('RxStateService', () => {
97100
});
98101

99102
describe('stateful with select', () => {
103+
beforeEach(() => {
104+
state = setupState({});
105+
});
106+
100107
it('should return empty state after init when subscribing late', () => {
101108
testScheduler.run(({ expectObservable }) => {
102-
const state = setupState({});
103109
expectObservable(state.select()).toBe('');
104110
});
105111
});
106112

107113
it('should return changes when subscribing late', () => {
108114
testScheduler.run(({ expectObservable }) => {
109-
const state = new RxState<PrimitiveState>();
110115
state.subscribe();
111-
112116
state.set({ num: 42 });
113117
expectObservable(state.select('num')).toBe('n', { n: 42 });
114118
});
115119
});
116120

117121
it('should return new changes', () => {
118-
testScheduler.run(({ expectObservable }) => {
119-
const state = new RxState<PrimitiveState>();
122+
testScheduler.run(() => {
120123
state.subscribe();
121124
state.set({ num: 42 });
122125
const slice$ = state.select('num');
@@ -189,7 +192,7 @@ describe('RxStateService', () => {
189192

190193
it('should return initial state', () => {
191194
testScheduler.run(({ expectObservable }) => {
192-
const state = new RxState<PrimitiveState>();
195+
const state = setupState({});
193196
state.subscribe();
194197

195198
state.set({ num: 42 });
@@ -282,6 +285,7 @@ describe('RxStateService', () => {
282285
state.set(initialPrimitiveState);
283286
state.select().subscribe((s) => expect(s).toBe(initialPrimitiveState));
284287
});
288+
285289
it('should override previous state slices', () => {
286290
const state = setupState({ initialState: initialPrimitiveState });
287291
state.select().subscribe((s) => {
@@ -301,6 +305,7 @@ describe('RxStateService', () => {
301305
).toThrowError('wrong param');
302306
});
303307
});
308+
304309
describe('with state project partial', () => {
305310
it('should add new slices', () => {
306311
const state = setupState({});
@@ -319,6 +324,7 @@ describe('RxStateService', () => {
319324
state.select().subscribe((s) => expect(state).toBe({ num: 43 }));
320325
});
321326
});
327+
322328
describe('with state key and value partial', () => {
323329
it('should add new slices', () => {
324330
const state = setupState<PrimitiveState>({});

libs/state/src/lib/rx-state.service.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Injectable, OnDestroy } from '@angular/core';
1+
import { ErrorHandler, inject, Injectable, OnDestroy } from '@angular/core';
22
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
33
import {
44
AccumulationFn,
@@ -53,8 +53,10 @@ export type ProjectValueReducer<T, K extends keyof T, V> = (
5353
@Injectable()
5454
export class RxState<T extends object> implements OnDestroy, Subscribable<T> {
5555
private subscription = new Subscription();
56-
57-
private accumulator = createAccumulationObservable<T>();
56+
private handleError = inject(ErrorHandler).handleError;
57+
private accumulator = createAccumulationObservable<T>({
58+
handleError: this.handleError,
59+
});
5860
private effectObservable = createSideEffectObservable();
5961

6062
/**

0 commit comments

Comments
 (0)