diff --git a/core/src/components/router/router.tsx b/core/src/components/router/router.tsx
index 28401b1df3..45e00d3718 100644
--- a/core/src/components/router/router.tsx
+++ b/core/src/components/router/router.tsx
@@ -6,7 +6,7 @@ import { debounce } from '../../utils/helpers';
import { ROUTER_INTENT_BACK, ROUTER_INTENT_FORWARD, ROUTER_INTENT_NONE } from './utils/constants';
import { printRedirects, printRoutes } from './utils/debug';
import { readNavState, waitUntilNavNode, writeNavState } from './utils/dom';
-import { routeRedirect, routerIDsToChain, routerPathToChain } from './utils/matching';
+import { findRouteRedirect, routerIDsToChain, routerPathToChain } from './utils/matching';
import { readRedirects, readRoutes } from './utils/parser';
import { chainToPath, generatePath, parsePath, readPath, writePath } from './utils/path';
@@ -188,7 +188,7 @@ export class Router implements ComponentInterface {
private onRedirectChanged() {
const path = this.getPath();
- if (path && routeRedirect(path, readRedirects(this.el))) {
+ if (path && findRouteRedirect(path, readRedirects(this.el))) {
this.writeNavStateRoot(path, ROUTER_INTENT_NONE);
}
}
@@ -226,13 +226,15 @@ export class Router implements ComponentInterface {
// lookup redirect rule
const redirects = readRedirects(this.el);
- const redirect = routeRedirect(path, redirects);
+ const redirect = findRouteRedirect(path, redirects);
let redirectFrom: string[] | null = null;
+
if (redirect) {
- this.setPath(redirect.to!, direction);
+ const { segments, queryString } = redirect.to!;
+ this.setPath(segments, direction, queryString);
redirectFrom = redirect.from;
- path = redirect.to!;
+ path = segments;
}
// lookup route chain
diff --git a/core/src/components/router/test/basic/index.html b/core/src/components/router/test/basic/index.html
index 57d0b3264d..635166f8af 100644
--- a/core/src/components/router/test/basic/index.html
+++ b/core/src/components/router/test/basic/index.html
@@ -70,7 +70,7 @@
- this is the first pahe
+ this is the first page
`;
}
}
@@ -211,6 +211,7 @@
+
diff --git a/core/src/components/router/test/basic/redirect.e2e.ts b/core/src/components/router/test/basic/redirect.e2e.ts
new file mode 100644
index 0000000000..fed23dbd6a
--- /dev/null
+++ b/core/src/components/router/test/basic/redirect.e2e.ts
@@ -0,0 +1,12 @@
+import { newE2EPage } from '@stencil/core/testing';
+
+test('redirect should support query string', async () => {
+ const page = await newE2EPage({
+ url: '/src/components/router/test/basic#/redirect-to-three?ionic:_testing=true'
+ });
+
+ await page.waitForChanges();
+
+ const url = await page.url();
+ expect(url).toContain('#/three?has_query_string=true');
+});
\ No newline at end of file
diff --git a/core/src/components/router/test/parser.spec.tsx b/core/src/components/router/test/parser.spec.tsx
index c067f9dc86..78c154333f 100644
--- a/core/src/components/router/test/parser.spec.tsx
+++ b/core/src/components/router/test/parser.spec.tsx
@@ -47,20 +47,22 @@ describe('parser', () => {
const r3 = mockRedirectElement(win, '*', null);
const r4 = mockRedirectElement(win, '/workout/*', '');
const r5 = mockRedirectElement(win, 'path/hey', '/path/to//login');
+ const r6 = mockRedirectElement(win, 'path/qs', '/path?qs=true');
root.appendChild(r1);
root.appendChild(r2);
root.appendChild(r3);
root.appendChild(r4);
root.appendChild(r5);
+ root.appendChild(r6);
const expected: RouteRedirect[] = [
{ from: [''], to: undefined },
- { from: [''], to: ['workout'] },
+ { from: [''], to: { segments: ['workout'] }},
{ from: ['*'], to: undefined },
- { from: ['workout', '*'], to: [''] },
- { from: ['path', 'hey'], to: ['path', 'to', 'login'] }
-
+ { from: ['workout', '*'], to: { segments: [''] } },
+ { from: ['path', 'hey'], to: { segments: ['path', 'to', 'login'] } },
+ { from: ['path', 'qs'], to: { segments: ['path'], queryString: 'qs=true' } },
];
expect(readRedirects(root)).toEqual(expected);
});
diff --git a/core/src/components/router/utils/debug.ts b/core/src/components/router/utils/debug.ts
index 1649a40b51..0e2206d6bf 100644
--- a/core/src/components/router/utils/debug.ts
+++ b/core/src/components/router/utils/debug.ts
@@ -16,7 +16,7 @@ export const printRedirects = (redirects: RouteRedirect[]) => {
console.group(`[ion-core] REDIRECTS[${redirects.length}]`);
for (const redirect of redirects) {
if (redirect.to) {
- console.debug('FROM: ', `$c ${generatePath(redirect.from)}`, 'font-weight: bold', ' TO: ', `$c ${generatePath(redirect.to)}`, 'font-weight: bold');
+ console.debug('FROM: ', `$c ${generatePath(redirect.from)}`, 'font-weight: bold', ' TO: ', `$c ${generatePath(redirect.to.segments)}`, 'font-weight: bold');
}
}
console.groupEnd();
diff --git a/core/src/components/router/utils/interface.ts b/core/src/components/router/utils/interface.ts
index a57e68f7cf..197bab8feb 100644
--- a/core/src/components/router/utils/interface.ts
+++ b/core/src/components/router/utils/interface.ts
@@ -18,7 +18,7 @@ export interface RouterEventDetail {
export interface RouteRedirect {
from: string[];
- to?: string[];
+ to?: ParsedRoute;
}
export interface RouteWrite {
@@ -45,6 +45,13 @@ export interface RouteNode extends RouteEntry {
children: RouteTree;
}
+export interface ParsedRoute {
+ // Parts of the route (non empty "/" separated parts of an URL).
+ segments: string[];
+ // Unparsed query string.
+ queryString?: string;
+}
+
export type RouterDirection = 'forward' | 'back' | 'root';
export type NavOutletElement = NavOutlet & HTMLStencilElement;
export type RouteChain = RouteEntry[];
diff --git a/core/src/components/router/utils/matching.ts b/core/src/components/router/utils/matching.ts
index 7b92df06d1..a975c7786f 100644
--- a/core/src/components/router/utils/matching.ts
+++ b/core/src/components/router/utils/matching.ts
@@ -1,12 +1,17 @@
import { RouteChain, RouteID, RouteRedirect } from './interface';
-export const matchesRedirect = (input: string[], route: RouteRedirect): route is RouteRedirect => {
- const { from, to } = route;
+// Returns whether the given redirect matches the given path segments.
+//
+// A redirect matches when the segments of the path and redirect.from are equal.
+// Note that segments are only checked until redirect.from contains a '*' which matches any path segment.
+// The path ['some', 'path', 'to', 'page'] matches both ['some', 'path', 'to', 'page'] and ['some', 'path', '*'].
+export const matchesRedirect = (path: string[], redirect: RouteRedirect): boolean => {
+ const { from, to } = redirect;
if (to === undefined) {
return false;
}
- if (from.length > input.length) {
+ if (from.length > path.length) {
return false;
}
@@ -15,15 +20,16 @@ export const matchesRedirect = (input: string[], route: RouteRedirect): route is
if (expected === '*') {
return true;
}
- if (expected !== input[i]) {
+ if (expected !== path[i]) {
return false;
}
}
- return from.length === input.length;
+ return from.length === path.length;
};
-export const routeRedirect = (path: string[], routes: RouteRedirect[]) => {
- return routes.find(route => matchesRedirect(path, route)) as RouteRedirect | undefined;
+// Returns the first redirect matching the path segments or undefined when no match found.
+export const findRouteRedirect = (path: string[], redirects: RouteRedirect[]) => {
+ return redirects.find(redirect => matchesRedirect(path, redirect));
};
export const matchesIDs = (ids: string[], chain: RouteChain): number => {
diff --git a/core/src/components/router/utils/parser.ts b/core/src/components/router/utils/parser.ts
index 08abfcd36a..3af2ffc203 100644
--- a/core/src/components/router/utils/parser.ts
+++ b/core/src/components/router/utils/parser.ts
@@ -8,7 +8,7 @@ export const readRedirects = (root: Element): RouteRedirect[] => {
const to = readProp(el, 'to');
return {
from: parsePath(readProp(el, 'from')).segments,
- to: to == null ? undefined : parsePath(to).segments,
+ to: to == null ? undefined : parsePath(to),
};
});
};
diff --git a/core/src/components/router/utils/path.ts b/core/src/components/router/utils/path.ts
index f005f5c78f..588fcd2097 100644
--- a/core/src/components/router/utils/path.ts
+++ b/core/src/components/router/utils/path.ts
@@ -1,6 +1,5 @@
-import { RouteChain, RouterDirection } from '../../../interface';
-
import { ROUTER_INTENT_FORWARD } from './constants';
+import { ParsedRoute, RouteChain, RouterDirection } from './interface';
export const generatePath = (segments: string[]): string => {
const path = segments
@@ -81,7 +80,7 @@ export const readPath = (loc: Location, root: string, useHash: boolean): string[
// Parses the path to:
// - segments an array of '/' separated parts,
// - queryString (undefined when no query string).
-export const parsePath = (path: string | undefined | null): {segments: string[], queryString?: string} => {
+export const parsePath = (path: string | undefined | null): ParsedRoute => {
let segments = [''];
let queryString;