diff --git a/CHANGELOG.md b/CHANGELOG.md index 3adc6a9af..ed45f5c47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ $ npm install @ngxs/store@dev - Refactor: Allow tree-shaking of dev-only code [#2259](https://github.com/ngxs/store/pull/2259) - Fix(store): Allow plain functions in `withNgxsPlugin` [#2255](https://github.com/ngxs/store/pull/2255) - Fix(store): Run plugins in injection context [#2256](https://github.com/ngxs/store/pull/2256) +- Fix(store): Setup unhandled error handler during NGXS initialization [#2263](https://github.com/ngxs/store/pull/2263) - Fix(websocket-plugin): Do not dispatch action when root injector is destroyed [#2257](https://github.com/ngxs/store/pull/2257) - Refactor(store): Replace `exhaustMap` [#2254](https://github.com/ngxs/store/pull/2254) - Refactor(store): Tree-shake development options token [#2260](https://github.com/ngxs/store/pull/2260) diff --git a/packages/store/src/internal/unhandled-rxjs-error-callback.ts b/packages/store/src/internal/unhandled-rxjs-error-callback.ts index dc5bd189e..f9f01aaac 100644 --- a/packages/store/src/internal/unhandled-rxjs-error-callback.ts +++ b/packages/store/src/internal/unhandled-rxjs-error-callback.ts @@ -2,17 +2,26 @@ import { config } from 'rxjs'; const ɵɵunhandledRxjsErrorCallbacks = new WeakMap(); -const existingHandler = config.onUnhandledError; -config.onUnhandledError = function (error: any) { - const unhandledErrorCallback = ɵɵunhandledRxjsErrorCallbacks.get(error); - if (unhandledErrorCallback) { - unhandledErrorCallback(); - } else if (existingHandler) { - existingHandler.call(this, error); - } else { - throw error; +let installed = false; +export function installOnUnhandhedErrorHandler(): void { + if (installed) { + return; } -}; + + const existingHandler = config.onUnhandledError; + config.onUnhandledError = function (error: any) { + const unhandledErrorCallback = ɵɵunhandledRxjsErrorCallbacks.get(error); + if (unhandledErrorCallback) { + unhandledErrorCallback(); + } else if (existingHandler) { + existingHandler.call(this, error); + } else { + throw error; + } + }; + + installed = true; +} export function executeUnhandledCallback(error: any) { const unhandledErrorCallback = ɵɵunhandledRxjsErrorCallbacks.get(error); diff --git a/packages/store/src/standalone-features/initializers.ts b/packages/store/src/standalone-features/initializers.ts index 406676e8c..eef13fd26 100644 --- a/packages/store/src/standalone-features/initializers.ts +++ b/packages/store/src/standalone-features/initializers.ts @@ -10,6 +10,7 @@ import { StatesAndDefaults } from '../internal/internals'; import { SelectFactory } from '../decorators/select/select-factory'; import { InternalStateOperations } from '../internal/state-operations'; import { LifecycleStateManager } from '../internal/lifecycle-state-manager'; +import { installOnUnhandhedErrorHandler } from '../internal/unhandled-rxjs-error-callback'; /** * This function is shared by both NgModule and standalone features. @@ -17,6 +18,12 @@ import { LifecycleStateManager } from '../internal/lifecycle-state-manager'; * same initialization functionality. */ export function rootStoreInitializer(): void { + // Override the RxJS `config.onUnhandledError` within the root store initializer, + // but only after other code has already executed. + // If users have a custom `config.onUnhandledError`, we might overwrite it too + // early and capture the original `config.onUnhandledError` before it is properly set. + installOnUnhandhedErrorHandler(); + const prebootFns = inject(NGXS_PREBOOT_FNS, { optional: true }) || []; prebootFns.forEach(prebootFn => prebootFn());