fix(vue): improve handling of parameterized urls (#22360)

resolves #22359
This commit is contained in:
Liam DeBeasi
2020-10-22 11:37:17 -04:00
committed by GitHub
parent 31f9bc81d6
commit 6fad0fe428
7 changed files with 124 additions and 45 deletions

View File

@ -27,20 +27,7 @@ export const createViewStacks = () => {
} }
const findLeavingViewItemByRouteInfo = (routeInfo: RouteInfo, outletId?: number) => { const findLeavingViewItemByRouteInfo = (routeInfo: RouteInfo, outletId?: number) => {
return findViewItemByPath(routeInfo.lastPathname, outletId); return findViewItemByPath(routeInfo.lastPathname, outletId, false);
}
const findViewItemByMatchedRoute = (matchedRoute: any, outletId: number): ViewItem | undefined => {
const stack = viewStacks[outletId];
if (!stack) return undefined;
return stack.find((viewItem: ViewItem) => {
if (viewItem.matchedRoute.path === matchedRoute.path) {
return viewItem;
}
return undefined;
});
} }
const findViewItemInStack = (path: string, stack: ViewItem[]): ViewItem | undefined => { const findViewItemInStack = (path: string, stack: ViewItem[]): ViewItem | undefined => {
@ -53,7 +40,7 @@ export const createViewStacks = () => {
}) })
} }
const findViewItemByPath = (path: string, outletId?: number): ViewItem | undefined => { const findViewItemByPath = (path: string, outletId?: number, strict: boolean = true): ViewItem | undefined => {
const matchView = (viewItem: ViewItem) => { const matchView = (viewItem: ViewItem) => {
const pathname = path; const pathname = path;
const viewItemPath = viewItem.matchedRoute.path; const viewItemPath = viewItem.matchedRoute.path;
@ -73,8 +60,10 @@ export const createViewStacks = () => {
const quickMatch = findViewItemInStack(path, stack); const quickMatch = findViewItemInStack(path, stack);
if (quickMatch) return quickMatch; if (quickMatch) return quickMatch;
if (!strict) {
const match = stack.find(matchView); const match = stack.find(matchView);
if (match) return match; if (match) return match;
}
} else { } else {
for (let outletId in viewStacks) { for (let outletId in viewStacks) {
const stack = viewStacks[outletId]; const stack = viewStacks[outletId];
@ -133,7 +122,6 @@ export const createViewStacks = () => {
return { return {
clear, clear,
findViewItemByRouteInfo, findViewItemByRouteInfo,
findViewItemByMatchedRoute,
findLeavingViewItemByRouteInfo, findLeavingViewItemByRouteInfo,
createViewItem, createViewItem,
getChildrenToRender, getChildrenToRender,

View File

@ -46,7 +46,19 @@ export const IonRouterOutlet = defineComponent({
// The base url for this router outlet // The base url for this router outlet
let parentOutletPath: string; let parentOutletPath: string;
watch(matchedRouteRef, () => setupViewItem(matchedRouteRef)); watch(matchedRouteRef, (currentValue, previousValue) => {
/**
* We need to make sure that we are not re-rendering
* the same view if navigation changes in a sub-outlet.
* This is mainly for tabs when outlet 1 renders ion-tabs
* and outlet 2 renders the individual tab view. We don't
* want outlet 1 creating a new ion-tabs instance every time
* we switch tabs.
*/
if (currentValue !== previousValue) {
setupViewItem(matchedRouteRef);
}
});
const canStart = () => { const canStart = () => {
const stack = viewStacks.getViewStack(id); const stack = viewStacks.getViewStack(id);
@ -273,19 +285,6 @@ export const IonRouterOutlet = defineComponent({
let enteringViewItem = viewStacks.findViewItemByRouteInfo(currentRoute, id); let enteringViewItem = viewStacks.findViewItemByRouteInfo(currentRoute, id);
if (!enteringViewItem) { if (!enteringViewItem) {
/**
* If we have no existing entering item, we need
* make sure that there is no existing view according to the
* matched route rather than what is in the url bar.
* This is mainly for tabs when outlet 1 renders ion-tabs
* and outlet 2 renders the individual tab view. We don't
* want outlet 1 creating a new ion-tabs instance every time
* we switch tabs.
*/
if (viewStacks.findViewItemByMatchedRoute(matchedRouteRef.value, id)) {
return;
}
enteringViewItem = viewStacks.createViewItem(id, matchedRouteRef.value.components.default, matchedRouteRef.value, currentRoute); enteringViewItem = viewStacks.createViewItem(id, matchedRouteRef.value.components.default, matchedRouteRef.value, currentRoute);
viewStacks.add(enteringViewItem); viewStacks.add(enteringViewItem);
} }

View File

@ -45,9 +45,13 @@ const routes: Array<RouteRecordRaw> = [
component: () => import('@/views/RoutingChild.vue') component: () => import('@/views/RoutingChild.vue')
}, },
{ {
path: '/routing/item/:id/view', path: '/routing/:id',
component: () => import('@/views/RoutingParameter.vue') component: () => import('@/views/RoutingParameter.vue')
}, },
{
path: '/routing/:id/view',
component: () => import('@/views/RoutingParameterView.vue')
},
{ {
path: '/navigation', path: '/navigation',
component: () => import('@/views/Navigation.vue') component: () => import('@/views/Navigation.vue')

View File

@ -24,8 +24,16 @@
<ion-label>Go to Child Page</ion-label> <ion-label>Go to Child Page</ion-label>
</ion-item> </ion-item>
<ion-item button router-link="/routing/item/123/view" id="item"> <ion-item button router-link="/routing/abc" id="parameter-abc">
<ion-label>Go to Parameterized Route</ion-label> <ion-label>Go to Parameter Page ABC</ion-label>
</ion-item>
<ion-item button router-link="/routing/xyz" id="parameter-xyz">
<ion-label>Go to Parameter Page XYZ</ion-label>
</ion-item>
<ion-item button router-link="/routing/123/view" id="parameter-view-item">
<ion-label>Go to Parameterized Page View</ion-label>
</ion-item> </ion-item>
</ion-content> </ion-content>
</ion-page> </ion-page>

View File

@ -16,8 +16,10 @@
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<div class="ion-padding"> <ion-button id="parameter-view" :router-link="'/routing/' + $route.params.id + '/view'">Go to Single View</ion-button>
Routing Parameter Page: {{ $route.params.id }}
<div class="ion-padding" id="parameter-value">
{{ $route.params.id }}
</div> </div>
</ion-content> </ion-content>
</ion-page> </ion-page>
@ -25,6 +27,7 @@
<script lang="ts"> <script lang="ts">
import { import {
IonButton,
IonBackButton, IonBackButton,
IonButtons, IonButtons,
IonContent, IonContent,
@ -37,6 +40,7 @@ import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
components: { components: {
IonButton,
IonBackButton, IonBackButton,
IonButtons, IonButtons,
IonContent, IonContent,

View File

@ -0,0 +1,49 @@
<template>
<ion-page data-pageid="routingparameterview">
<ion-header :translucent="true">
<ion-toolbar>
<ion-buttons>
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Routing Parameter View</ion-title>
</ion-toolbar>
</ion-header>
<ion-content :fullscreen="true">
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">Routing Parameter View</ion-title>
</ion-toolbar>
</ion-header>
<div class="ion-padding">
{{ $route.params.id }}
</div>
</ion-content>
</ion-page>
</template>
<script lang="ts">
import {
IonBackButton,
IonButtons,
IonContent,
IonHeader,
IonPage,
IonTitle,
IonToolbar
} from '@ionic/vue';
import { defineComponent } from 'vue';
export default defineComponent({
components: {
IonBackButton,
IonButtons,
IonContent,
IonHeader,
IonPage,
IonTitle,
IonToolbar
}
});
</script>

View File

@ -48,19 +48,46 @@ describe('Routing', () => {
cy.ionPageDoesNotExist('routingchild') cy.ionPageDoesNotExist('routingchild')
}); });
// Verifies fix for https://github.com/ionic-team/ionic-framework/issues/22324 // Verifies fix for https://github.com/ionic-team/ionic-framework/issues/22359
it('should show correct view when navigating back from parameterized page to query string page', () => { it('should navigate to multiple pages that match the same parameterized route', () => {
cy.visit('http://localhost:8080'); cy.visit('http://localhost:8080/routing');
cy.get('#routing').click();
cy.get('#route-params').click();
cy.get('#item').click();
cy.get('#parameter-abc').click();
cy.ionPageVisible('routingparameter'); cy.ionPageVisible('routingparameter');
cy.ionPageHidden('routing'); cy.get('[data-pageid=routingparameter] #parameter-value').should('have.text', 'abc');
cy.ionBackClick('routingparameter'); cy.ionBackClick('routingparameter');
cy.ionPageDoesNotExist('routingparameter'); cy.ionPageDoesNotExist('routingparameter');
cy.get('#parameter-xyz').click();
cy.ionPageVisible('routingparameter');
cy.get('[data-pageid=routingparameter] #parameter-value').should('have.text', 'xyz');
});
// Verifies fix for https://github.com/ionic-team/ionic-framework/issues/22359
it('should handle parameterized urls properly', () => {
cy.visit('http://localhost:8080/routing');
cy.get('#parameter-abc').click();
cy.ionPageVisible('routingparameter');
cy.get('#parameter-view').click();
cy.ionPageVisible('routingparameterview');
});
// Verifies fix for https://github.com/ionic-team/ionic-framework/issues/22324
it('should show correct view when navigating back from parameterized page to query string page', () => {
cy.visit('http://localhost:8080/routing');
cy.get('#route-params').click();
cy.get('#parameter-view-item').click();
cy.ionPageVisible('routingparameterview');
cy.ionPageHidden('routing');
cy.ionBackClick('routingparameterview');
cy.ionPageDoesNotExist('routingparameterview');
cy.ionPageVisible('routing'); cy.ionPageVisible('routing');
}); });