Skip to content

Commit

Permalink
fix(store): add root store initializer guard
Browse files Browse the repository at this point in the history
  • Loading branch information
arturovt committed Dec 17, 2024
1 parent 7869eae commit 3870cb0
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 0 deletions.
5 changes: 5 additions & 0 deletions packages/store/src/standalone-features/initializers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { InitState, UpdateState } from '../plugin_api';
import { FEATURE_STATE_TOKEN, ROOT_STATE_TOKEN } from '../symbols';
import { StateFactory } from '../internal/state-factory';
import { StatesAndDefaults } from '../internal/internals';
import { assertRootStoreNotInitialized } from './root-guard';
import { SelectFactory } from '../decorators/select/select-factory';
import { InternalStateOperations } from '../internal/state-operations';
import { LifecycleStateManager } from '../internal/lifecycle-state-manager';
Expand All @@ -18,6 +19,10 @@ import { installOnUnhandhedErrorHandler } from '../internal/unhandled-rxjs-error
* same initialization functionality.
*/
export function rootStoreInitializer(): void {
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
assertRootStoreNotInitialized();
}

// 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
Expand Down
14 changes: 14 additions & 0 deletions packages/store/src/standalone-features/root-guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { inject, InjectionToken } from '@angular/core';

export const ROOT_STORE_GUARD = /* @__PURE__ */ new InjectionToken('ROOT_STORE_GUARD', {
providedIn: 'root',
factory: () => ({ initialized: false })
});

export function assertRootStoreNotInitialized(): void {
const rootStoreGuard = inject(ROOT_STORE_GUARD);
if (rootStoreGuard.initialized) {
throw new Error('provideStore() should only be called once.');
}
rootStoreGuard.initialized = true;
}
43 changes: 43 additions & 0 deletions packages/store/tests/issues/issue-2277-provide-store-twice.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ApplicationConfig, Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter, Router } from '@angular/router';
import { provideStore } from '@ngxs/store';
import { freshPlatform, skipConsoleLogging } from '@ngxs/store/internals/testing';

describe('provideStore() being called twice', () => {
@Component({ selector: 'app-root', template: '' })
class TestComponent {}

@Component({ template: '' })
class RouteComponent {}

const appConfig: ApplicationConfig = {
providers: [
provideStore(),
provideRouter([
{ path: 'route', loadComponent: () => RouteComponent, providers: [provideStore()] }
])
]
};

it(
'should throw an error when provideStore() is called twice',
freshPlatform(async () => {
// Arrange
expect.hasAssertions();

// Act
const { injector } = await skipConsoleLogging(() =>
bootstrapApplication(TestComponent, appConfig)
);
const router = injector.get(Router);

try {
await router.navigateByUrl('/route');
} catch (error) {
// Assert
expect(error.message).toEqual('provideStore() should only be called once.');
}
})
);
});

0 comments on commit 3870cb0

Please sign in to comment.