Skip to content

Commit b638698

Browse files
fix(devtools): fix existing name error in SSR
devtools use a static variable to store the existing names of SignalStore classes. In SSR, this fails because it registers a second time in the browser and runs see this the same name already exists.
1 parent ad09e31 commit b638698

File tree

4 files changed

+60
-20
lines changed

4 files changed

+60
-20
lines changed

libs/ngrx-toolkit/src/lib/devtools/internal/devtools-syncer.service.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,9 @@ export class DevtoolsSyncer implements OnDestroy {
116116
const names = Object.values(this.#stores).map((store) => store.name);
117117

118118
if (names.includes(storeName)) {
119-
const { options } = throwIfNull(
120-
Object.values(this.#stores).find((store) => store.name === storeName)
121-
);
119+
// const { options } = throwIfNull(
120+
// Object.values(this.#stores).find((store) => store.name === storeName)
121+
// );
122122
if (!options.indexNames) {
123123
throw new Error(`An instance of the store ${storeName} already exists. \
124124
Enable automatic indexing via withDevTools('${storeName}', { indexNames: true }), or rename it upon instantiation.`);

libs/ngrx-toolkit/src/lib/devtools/tests/helpers.spec.ts

-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { existingNames } from '../with-devtools';
21
import { PLATFORM_ID } from '@angular/core';
32
import { TestBed } from '@angular/core/testing';
43

@@ -35,8 +34,6 @@ export function setupExtensions(
3534
});
3635
}
3736

38-
existingNames.clear();
39-
4037
return { sendSpy, connectSpy };
4138
}
4239

libs/ngrx-toolkit/src/lib/devtools/tests/naming.spec.ts

+50-4
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,58 @@ Enable automatic indexing via withDevTools('flights', { indexNames: true }), or
103103
);
104104
});
105105

106-
it('should throw if name already exists', () => {
106+
it('should index for two different stores with same devtools name', () => {
107+
const { sendSpy } = setupExtensions();
108+
109+
TestBed.inject(
110+
signalStore({ providedIn: 'root' }, withDevtools('flights'))
111+
);
112+
TestBed.inject(
113+
signalStore({ providedIn: 'root' }, withDevtools('flights'))
114+
);
115+
116+
TestBed.flushEffects();
117+
expect(sendSpy.mock.calls).toEqual([
118+
[
119+
{ type: 'Store Update' },
120+
{
121+
flights: {},
122+
'flights-1': {},
123+
},
124+
],
125+
]);
126+
});
127+
128+
it('should throw for two different stores when indexing is disabled', () => {
129+
setupExtensions();
130+
131+
TestBed.inject(
132+
signalStore({ providedIn: 'root' }, withDevtools('flights'))
133+
);
134+
expect(() =>
135+
TestBed.inject(
136+
signalStore(
137+
{ providedIn: 'root' },
138+
withDevtools('flights', withDisabledNameIndices())
139+
)
140+
)
141+
).toThrow();
142+
});
143+
144+
it('should not throw for two different stores if only the first one has indexing disabled', () => {
107145
setupExtensions();
108-
signalStore(withDevtools('flights'));
109-
expect(() => signalStore(withDevtools('flights'))).toThrow(
110-
'The store "flights" has already been registered in the DevTools. Duplicate registration is not allowed.'
146+
147+
TestBed.inject(
148+
signalStore(
149+
{ providedIn: 'root' },
150+
withDevtools('flights', withDisabledNameIndices())
151+
)
111152
);
153+
expect(() =>
154+
TestBed.inject(
155+
signalStore({ providedIn: 'root' }, withDevtools('flights'))
156+
)
157+
).not.toThrow();
112158
});
113159

114160
describe('renaming', () => {

libs/ngrx-toolkit/src/lib/devtools/with-devtools.ts

+7-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { signalStoreFeature, withHooks, withMethods } from '@ngrx/signals';
2-
import { inject } from '@angular/core';
2+
import { inject, InjectionToken } from '@angular/core';
33
import { DevtoolsSyncer } from './internal/devtools-syncer.service';
44
import {
55
DevtoolsFeature,
@@ -14,11 +14,14 @@ declare global {
1414
}
1515
}
1616

17-
export const existingNames = new Map<string, unknown>();
18-
1917
export const renameDevtoolsMethodName = '___renameDevtoolsName';
2018
export const uniqueDevtoolsId = '___uniqueDevtoolsId';
2119

20+
const EXISTING_NAMES = new InjectionToken(
21+
'Array contain existing names for the signal stores',
22+
{ factory: () => [] as string[], providedIn: 'root' }
23+
);
24+
2225
/**
2326
* Adds this store as a feature state to the Redux DevTools.
2427
*
@@ -33,16 +36,10 @@ export const uniqueDevtoolsId = '___uniqueDevtoolsId';
3336
* @param features features to extend or modify the behavior of the Devtools
3437
*/
3538
export function withDevtools(name: string, ...features: DevtoolsFeature[]) {
36-
if (existingNames.has(name)) {
37-
throw new Error(
38-
`The store "${name}" has already been registered in the DevTools. Duplicate registration is not allowed.`
39-
);
40-
}
41-
existingNames.set(name, true);
42-
4339
return signalStoreFeature(
4440
withMethods(() => {
4541
const syncer = inject(DevtoolsSyncer);
42+
4643
const id = syncer.getNextId();
4744

4845
// TODO: use withProps and symbols

0 commit comments

Comments
 (0)