diff --git a/core/api.txt b/core/api.txt index fa5dfdeb3d2..82b9846d8ce 100644 --- a/core/api.txt +++ b/core/api.txt @@ -1045,7 +1045,7 @@ ion-router,none ion-router,prop,root,string,'/',false,false ion-router,prop,useHash,boolean,true,false,false ion-router,method,back,back() => Promise -ion-router,method,push,push(url: string, direction?: RouterDirection, animation?: AnimationBuilder | undefined) => Promise +ion-router,method,push,push(path: string, direction?: RouterDirection, animation?: AnimationBuilder | undefined) => Promise ion-router,event,ionRouteDidChange,RouterEventDetail,true ion-router,event,ionRouteWillChange,RouterEventDetail,true diff --git a/core/src/components/nav/view-controller.ts b/core/src/components/nav/view-controller.ts index 004eebbf119..e12c36a95eb 100644 --- a/core/src/components/nav/view-controller.ts +++ b/core/src/components/nav/view-controller.ts @@ -1,6 +1,6 @@ import { AnimationBuilder, ComponentProps, FrameworkDelegate, NavComponentWithProps } from '../../interface'; import { attachComponent } from '../../utils/framework-delegate'; -import { assert } from '../../utils/helpers'; +import { assert, shallowEqualStringMap } from '../../utils/helpers'; export const VIEW_STATE_NEW = 1; export const VIEW_STATE_ATTACHED = 2; @@ -54,29 +54,8 @@ export const matches = (view: ViewController | undefined, id: string, params: Co if (view.component !== id) { return false; } - const currentParams = view.params; - if (currentParams === params) { - return true; - } - if (!currentParams && !params) { - return true; - } - if (!currentParams || !params) { - return false; - } - const keysA = Object.keys(currentParams); - const keysB = Object.keys(params); - if (keysA.length !== keysB.length) { - return false; - } - // Test for A's keys different from B. - for (const key of keysA) { - if (currentParams[key] !== params[key]) { - return false; - } - } - return true; + return shallowEqualStringMap(view.params, params); }; export const convertToView = (page: any, params: ComponentProps | undefined): ViewController | null => { diff --git a/core/src/components/router-outlet/route-outlet.tsx b/core/src/components/router-outlet/route-outlet.tsx index 173662790db..b7099790c56 100644 --- a/core/src/components/router-outlet/route-outlet.tsx +++ b/core/src/components/router-outlet/route-outlet.tsx @@ -5,6 +5,7 @@ import { getIonMode } from '../../global/ionic-global'; import { Animation, AnimationBuilder, ComponentProps, ComponentRef, FrameworkDelegate, Gesture, NavOutlet, RouteID, RouteWrite, RouterDirection, RouterOutletOptions, SwipeGestureHandler } from '../../interface'; import { getTimeGivenProgression } from '../../utils/animation/cubic-bezier'; import { attachComponent, detachComponent } from '../../utils/framework-delegate'; +import { shallowEqualStringMap } from '../../utils/helpers'; import { transition } from '../../utils/transition'; @Component({ @@ -159,7 +160,7 @@ export class RouterOutlet implements ComponentInterface, NavOutlet { } private async setRoot(component: ComponentRef, params?: ComponentProps, opts?: RouterOutletOptions): Promise { - if (this.activeComponent === component) { + if (this.activeComponent === component && shallowEqualStringMap(params, this.activeParams)) { return false; } diff --git a/core/src/components/router-outlet/test/basic/e2e.ts b/core/src/components/router-outlet/test/basic/e2e.ts index f58b8af2002..5ed9c15b42d 100644 --- a/core/src/components/router-outlet/test/basic/e2e.ts +++ b/core/src/components/router-outlet/test/basic/e2e.ts @@ -26,4 +26,18 @@ test('getRouteId() should return the route parameters', async () => { expect(routeId.id).toEqual('PAGE-THREE'); expect(routeId.params).toEqual({ param: 'route' }); +}); + +test('it should be possible to activate the same component provided parameters are different', async () => { + const page = await newE2EPage({ + url: '/src/components/router-outlet/test/basic?ionic:_testing=true' + }); + + await page.$eval('ion-item[href="#/page-4.1/foo"] ion-label', (el: any) => el.click()); + await page.waitForChanges(); + expect(await page.$eval('ion-router-outlet', (el) => el.textContent)).toMatch(/text = foo/); + + await page.$eval('ion-item[href="#/page-4.2/bar"] ion-label', (el: any) => el.click()); + await page.waitForChanges(); + expect(await page.$eval('ion-router-outlet', (el) => el.textContent)).toMatch(/text = bar/); }); \ No newline at end of file diff --git a/core/src/components/router-outlet/test/basic/index.html b/core/src/components/router-outlet/test/basic/index.html index d952829b129..b21e7f22721 100644 --- a/core/src/components/router-outlet/test/basic/index.html +++ b/core/src/components/router-outlet/test/basic/index.html @@ -58,9 +58,24 @@

Page Three

`; } } + class PageFour extends HTMLElement { + connectedCallback() { + this.innerHTML = ` + + + Page Four + + + +

text = ${this.text}

+
+ `; + } + } customElements.define('page-one', PageOne); customElements.define('page-two', PageTwo); customElements.define('page-three', PageThree); + customElements.define('page-four', PageFour); @@ -70,6 +85,8 @@

Page Three

+ + @@ -89,6 +106,12 @@

Page Three

Page 3 + + Page 4 (foo) + + + Page 4 (bar) + diff --git a/core/src/utils/helpers.ts b/core/src/utils/helpers.ts index eaaf1d968d8..6f977dc9c74 100644 --- a/core/src/utils/helpers.ts +++ b/core/src/utils/helpers.ts @@ -348,3 +348,36 @@ export const debounce = (func: (...args: any[]) => void, wait = 0) => { timer = setTimeout(func, wait, ...args); }; }; + +/** + * Check whether the two string maps are shallow equal. + * + * undefined is treated as an empty map. + * + * @returns whether the keys are the same and the values are shallow equal. + */ +export const shallowEqualStringMap = (map1: {[k: string]: any} | undefined, map2: {[k: string]: any} | undefined): boolean => { + map1 ??= {}; + map2 ??= {}; + + if (map1 === map2) { + return true; + } + + const keys1 = Object.keys(map1); + + if (keys1.length !== Object.keys(map2).length) { + return false; + } + + for (const k1 of keys1) { + if (!(k1 in map2)) { + return false; + } + if (map1[k1] !== map2[k1]) { + return false; + } + } + + return true; +} \ No newline at end of file