fix(router-outlet): navigating to same route with different params now activates component (#24760)

resolves #24653
This commit is contained in:
Victor Berchet
2022-02-11 07:08:53 -08:00
committed by GitHub
parent 7b3838cc67
commit abc36ae80b
6 changed files with 75 additions and 25 deletions

View File

@ -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<void>
ion-router,method,push,push(url: string, direction?: RouterDirection, animation?: AnimationBuilder | undefined) => Promise<boolean>
ion-router,method,push,push(path: string, direction?: RouterDirection, animation?: AnimationBuilder | undefined) => Promise<boolean>
ion-router,event,ionRouteDidChange,RouterEventDetail,true
ion-router,event,ionRouteWillChange,RouterEventDetail,true

View File

@ -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 => {

View File

@ -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<boolean> {
if (this.activeComponent === component) {
if (this.activeComponent === component && shallowEqualStringMap(params, this.activeParams)) {
return false;
}

View File

@ -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/);
});

View File

@ -58,9 +58,24 @@
`;
}
}
class PageFour extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-title>Page Four</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<h1>text = ${this.text}</h1>
</ion-content>
`;
}
}
customElements.define('page-one', PageOne);
customElements.define('page-two', PageTwo);
customElements.define('page-three', PageThree);
customElements.define('page-four', PageFour);
</script>
</head>
@ -70,6 +85,8 @@
<ion-route url="/" component="page-one"> </ion-route>
<ion-route url="/two/:param" component="page-two"> </ion-route>
<ion-route url="/page-3" component="page-three"> </ion-route>
<ion-route url="/page-4.1/:text" component="page-four"> </ion-route>
<ion-route url="/page-4.2/:text" component="page-four"> </ion-route>
</ion-router>
<ion-split-pane content-id="main">
@ -89,6 +106,12 @@
<ion-item href="#/page-3">
<ion-label>Page 3</ion-label>
</ion-item>
<ion-item href="#/page-4.1/foo">
<ion-label>Page 4 (foo)</ion-label>
</ion-item>
<ion-item href="#/page-4.2/bar">
<ion-label>Page 4 (bar)</ion-label>
</ion-item>
</ion-content>
</ion-menu>
<ion-router-outlet id="main"></ion-router-outlet>

View File

@ -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;
}