diff --git a/modules/router-store/spec/integration.spec.ts b/modules/router-store/spec/integration.spec.ts index 997cd8a6ed..46e9ffb535 100644 --- a/modules/router-store/spec/integration.spec.ts +++ b/modules/router-store/spec/integration.spec.ts @@ -18,6 +18,7 @@ import { ROUTER_NAVIGATED, ROUTER_NAVIGATION, ROUTER_REQUEST, + routerNavigationAction, RouterAction, routerReducer, RouterReducerState, @@ -169,6 +170,32 @@ describe('integration spec', () => { }); }); + it('should ignore routing actions for the URL that is currently open', async () => { + createTestModule({ + reducers: { router: routerReducer }, + }); + + const router = TestBed.inject(Router); + const store = TestBed.inject(Store); + const navigateByUrlSpy = jest.spyOn(router, 'navigateByUrl'); + + await router.navigateByUrl('/'); + + const SAME_URL_WITHOUT_SLASH = ''; + + store.dispatch( + routerNavigationAction({ + payload: { + routerState: { url: SAME_URL_WITHOUT_SLASH, root: {} as any }, + event: { id: 123 } as any, + }, + }) + ); + + // Navigates only ONCE 👇 + expect(navigateByUrlSpy.mock.calls.length).toBe(1); + }); + it('should support rolling back if navigation gets canceled (navigation initialized through router)', (done: any) => { const reducer = (state: string = '', action: RouterAction): any => { if (action.type === ROUTER_NAVIGATION) { @@ -573,7 +600,7 @@ describe('integration spec', () => { expect(log).toEqual([ { type: 'store', state: null }, // initial state - { type: 'store', state: null }, // ROUTER_REQEST event in the store + { type: 'store', state: null }, // ROUTER_REQUEST event in the store { type: 'action', action: ROUTER_REQUEST }, { type: 'router', event: 'NavigationStart', url: '/load' }, { type: 'store', state: { url: '', navigationId: 1 } }, diff --git a/modules/router-store/src/router_store_module.ts b/modules/router-store/src/router_store_module.ts index e804709fc9..2af4e8a1c8 100644 --- a/modules/router-store/src/router_store_module.ts +++ b/modules/router-store/src/router_store_module.ts @@ -219,7 +219,7 @@ export class StoreRouterConnectingModule { } const url = routerStoreState.state.url; - if (this.router.url !== url) { + if (!isSameUrl(this.router.url, url)) { this.storeState = storeState; this.trigger = RouterTrigger.STORE; this.router.navigateByUrl(url).catch((error) => { @@ -347,3 +347,17 @@ export class StoreRouterConnectingModule { this.routerState = null; } } + +/** + * Check if the URLs are matching. Accounts for the possibility of trailing "/" in url. + */ +function isSameUrl(first: string, second: string): boolean { + return stripTrailingSlash(first) === stripTrailingSlash(second); +} + +function stripTrailingSlash(text: string): string { + if (text.length > 0 && text[text.length - 1] === '/') { + return text.substring(0, text.length - 1); + } + return text; +}