Skip to content

Commit abc36ae

Browse files
authored
fix(router-outlet): navigating to same route with different params now activates component (#24760)
resolves #24653
1 parent 7b3838c commit abc36ae

File tree

6 files changed

+75
-25
lines changed

6 files changed

+75
-25
lines changed

core/api.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1045,7 +1045,7 @@ ion-router,none
10451045
ion-router,prop,root,string,'/',false,false
10461046
ion-router,prop,useHash,boolean,true,false,false
10471047
ion-router,method,back,back() => Promise<void>
1048-
ion-router,method,push,push(url: string, direction?: RouterDirection, animation?: AnimationBuilder | undefined) => Promise<boolean>
1048+
ion-router,method,push,push(path: string, direction?: RouterDirection, animation?: AnimationBuilder | undefined) => Promise<boolean>
10491049
ion-router,event,ionRouteDidChange,RouterEventDetail,true
10501050
ion-router,event,ionRouteWillChange,RouterEventDetail,true
10511051

core/src/components/nav/view-controller.ts

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AnimationBuilder, ComponentProps, FrameworkDelegate, NavComponentWithProps } from '../../interface';
22
import { attachComponent } from '../../utils/framework-delegate';
3-
import { assert } from '../../utils/helpers';
3+
import { assert, shallowEqualStringMap } from '../../utils/helpers';
44

55
export const VIEW_STATE_NEW = 1;
66
export const VIEW_STATE_ATTACHED = 2;
@@ -54,29 +54,8 @@ export const matches = (view: ViewController | undefined, id: string, params: Co
5454
if (view.component !== id) {
5555
return false;
5656
}
57-
const currentParams = view.params;
58-
if (currentParams === params) {
59-
return true;
60-
}
61-
if (!currentParams && !params) {
62-
return true;
63-
}
64-
if (!currentParams || !params) {
65-
return false;
66-
}
67-
const keysA = Object.keys(currentParams);
68-
const keysB = Object.keys(params);
69-
if (keysA.length !== keysB.length) {
70-
return false;
71-
}
7257

73-
// Test for A's keys different from B.
74-
for (const key of keysA) {
75-
if (currentParams[key] !== params[key]) {
76-
return false;
77-
}
78-
}
79-
return true;
58+
return shallowEqualStringMap(view.params, params);
8059
};
8160

8261
export const convertToView = (page: any, params: ComponentProps | undefined): ViewController | null => {

core/src/components/router-outlet/route-outlet.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { getIonMode } from '../../global/ionic-global';
55
import { Animation, AnimationBuilder, ComponentProps, ComponentRef, FrameworkDelegate, Gesture, NavOutlet, RouteID, RouteWrite, RouterDirection, RouterOutletOptions, SwipeGestureHandler } from '../../interface';
66
import { getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
77
import { attachComponent, detachComponent } from '../../utils/framework-delegate';
8+
import { shallowEqualStringMap } from '../../utils/helpers';
89
import { transition } from '../../utils/transition';
910

1011
@Component({
@@ -159,7 +160,7 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
159160
}
160161

161162
private async setRoot(component: ComponentRef, params?: ComponentProps, opts?: RouterOutletOptions): Promise<boolean> {
162-
if (this.activeComponent === component) {
163+
if (this.activeComponent === component && shallowEqualStringMap(params, this.activeParams)) {
163164
return false;
164165
}
165166

core/src/components/router-outlet/test/basic/e2e.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,18 @@ test('getRouteId() should return the route parameters', async () => {
2626

2727
expect(routeId.id).toEqual('PAGE-THREE');
2828
expect(routeId.params).toEqual({ param: 'route' });
29+
});
30+
31+
test('it should be possible to activate the same component provided parameters are different', async () => {
32+
const page = await newE2EPage({
33+
url: '/src/components/router-outlet/test/basic?ionic:_testing=true'
34+
});
35+
36+
await page.$eval('ion-item[href="#/page-4.1/foo"] ion-label', (el: any) => el.click());
37+
await page.waitForChanges();
38+
expect(await page.$eval('ion-router-outlet', (el) => el.textContent)).toMatch(/text = foo/);
39+
40+
await page.$eval('ion-item[href="#/page-4.2/bar"] ion-label', (el: any) => el.click());
41+
await page.waitForChanges();
42+
expect(await page.$eval('ion-router-outlet', (el) => el.textContent)).toMatch(/text = bar/);
2943
});

core/src/components/router-outlet/test/basic/index.html

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,24 @@ <h1>Page Three</h1>
5858
`;
5959
}
6060
}
61+
class PageFour extends HTMLElement {
62+
connectedCallback() {
63+
this.innerHTML = `
64+
<ion-header>
65+
<ion-toolbar>
66+
<ion-title>Page Four</ion-title>
67+
</ion-toolbar>
68+
</ion-header>
69+
<ion-content class="ion-padding">
70+
<h1>text = ${this.text}</h1>
71+
</ion-content>
72+
`;
73+
}
74+
}
6175
customElements.define('page-one', PageOne);
6276
customElements.define('page-two', PageTwo);
6377
customElements.define('page-three', PageThree);
78+
customElements.define('page-four', PageFour);
6479
</script>
6580
</head>
6681

@@ -70,6 +85,8 @@ <h1>Page Three</h1>
7085
<ion-route url="/" component="page-one"> </ion-route>
7186
<ion-route url="/two/:param" component="page-two"> </ion-route>
7287
<ion-route url="/page-3" component="page-three"> </ion-route>
88+
<ion-route url="/page-4.1/:text" component="page-four"> </ion-route>
89+
<ion-route url="/page-4.2/:text" component="page-four"> </ion-route>
7390
</ion-router>
7491

7592
<ion-split-pane content-id="main">
@@ -89,6 +106,12 @@ <h1>Page Three</h1>
89106
<ion-item href="#/page-3">
90107
<ion-label>Page 3</ion-label>
91108
</ion-item>
109+
<ion-item href="#/page-4.1/foo">
110+
<ion-label>Page 4 (foo)</ion-label>
111+
</ion-item>
112+
<ion-item href="#/page-4.2/bar">
113+
<ion-label>Page 4 (bar)</ion-label>
114+
</ion-item>
92115
</ion-content>
93116
</ion-menu>
94117
<ion-router-outlet id="main"></ion-router-outlet>

core/src/utils/helpers.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,3 +348,36 @@ export const debounce = (func: (...args: any[]) => void, wait = 0) => {
348348
timer = setTimeout(func, wait, ...args);
349349
};
350350
};
351+
352+
/**
353+
* Check whether the two string maps are shallow equal.
354+
*
355+
* undefined is treated as an empty map.
356+
*
357+
* @returns whether the keys are the same and the values are shallow equal.
358+
*/
359+
export const shallowEqualStringMap = (map1: {[k: string]: any} | undefined, map2: {[k: string]: any} | undefined): boolean => {
360+
map1 ??= {};
361+
map2 ??= {};
362+
363+
if (map1 === map2) {
364+
return true;
365+
}
366+
367+
const keys1 = Object.keys(map1);
368+
369+
if (keys1.length !== Object.keys(map2).length) {
370+
return false;
371+
}
372+
373+
for (const k1 of keys1) {
374+
if (!(k1 in map2)) {
375+
return false;
376+
}
377+
if (map1[k1] !== map2[k1]) {
378+
return false;
379+
}
380+
}
381+
382+
return true;
383+
}

0 commit comments

Comments
 (0)