diff --git a/packages/core/src/components.d.ts b/packages/core/src/components.d.ts index 659421386c..c400a6120e 100644 --- a/packages/core/src/components.d.ts +++ b/packages/core/src/components.d.ts @@ -2604,6 +2604,7 @@ declare global { component?: string; params?: {[key: string]: any}; path?: string; + redirectTo?: string; } } } diff --git a/packages/core/src/components/route/readme.md b/packages/core/src/components/route/readme.md index 185af3066f..24fcd82c4e 100644 --- a/packages/core/src/components/route/readme.md +++ b/packages/core/src/components/route/readme.md @@ -22,6 +22,11 @@ string string +#### redirectTo + +string + + ## Attributes #### component @@ -39,6 +44,11 @@ string string +#### redirect-to + +string + + ---------------------------------------------- diff --git a/packages/core/src/components/route/route.tsx b/packages/core/src/components/route/route.tsx index 9e0afae050..c59464ed8c 100644 --- a/packages/core/src/components/route/route.tsx +++ b/packages/core/src/components/route/route.tsx @@ -7,5 +7,6 @@ import { Component, Prop } from '@stencil/core'; export class Route { @Prop() path = ''; @Prop() component: string; + @Prop() redirectTo: string; @Prop() params: {[key: string]: any}; } diff --git a/packages/core/src/components/router/router.tsx b/packages/core/src/components/router/router.tsx index 2bd559f98c..157d2bea3d 100644 --- a/packages/core/src/components/router/router.tsx +++ b/packages/core/src/components/router/router.tsx @@ -1,10 +1,10 @@ import { Build, Component, Element, Listen, Method, Prop } from '@stencil/core'; import { Config, DomController } from '../../index'; -import { flattenRouterTree, readRoutes } from './utils/parser'; +import { flattenRouterTree, readRoutes, readRedirects } from './utils/parser'; import { readNavState, writeNavState } from './utils/dom'; import { chainToPath, generatePath, parsePath, readPath, writePath } from './utils/path'; -import { RouteChain } from './utils/interfaces'; -import { routerIDsToChain, routerPathToChain } from './utils/matching'; +import { RouteChain, RouteRedirect } from './utils/interfaces'; +import { routerIDsToChain, routerPathToChain, routeRedirect } from './utils/matching'; @Component({ @@ -13,6 +13,7 @@ import { routerIDsToChain, routerPathToChain } from './utils/matching'; export class Router { private routes: RouteChain[]; + private redirects: RouteRedirect[]; private busy = false; private state = 0; @@ -28,6 +29,7 @@ export class Router { // read config const tree = readRoutes(this.el); this.routes = flattenRouterTree(tree); + this.redirects = readRedirects(this.el); if (Build.isDev) { console.debug('%c[@ionic/core]', 'font-weight: bold', `ion-router registered ${this.routes.length} routes`); @@ -42,7 +44,7 @@ export class Router { // perform first write this.dom.raf(() => { console.debug('[OUT] page load -> write nav state'); - this.writeNavStateRoot(); + this.onPopState(); }); } @@ -52,7 +54,7 @@ export class Router { this.state++; window.history.replaceState(this.state, document.title, document.location.href); } - this.writeNavStateRoot(); + this.writeNavStateRoot(this.readPath()); } @Method() @@ -80,21 +82,23 @@ export class Router { @Method() push(url: string, backDirection = false) { - this.writePath(parsePath(url), backDirection); - return this.writeNavStateRoot(); + const path = parsePath(url); + this.writePath(path, backDirection); + return this.writeNavStateRoot(path); } - private writeNavStateRoot(): Promise { + private writeNavStateRoot(path: string[]): Promise { if (this.busy) { return Promise.resolve(); } - const currentPath = this.readPath(); - if (!currentPath) { - return Promise.resolve(); + const redirect = routeRedirect(path, this.redirects); + if (redirect) { + this.writePath(redirect.to, true); + path = redirect.to; } const direction = window.history.state >= this.state ? 1 : -1; const node = document.querySelector('ion-app'); - const chain = routerPathToChain(currentPath, this.routes); + const chain = routerPathToChain(path, this.routes); return this.writeNavState(node, chain, direction); } diff --git a/packages/core/src/components/router/utils/interfaces.ts b/packages/core/src/components/router/utils/interfaces.ts index f7a26aa2a6..35b932ad10 100644 --- a/packages/core/src/components/router/utils/interfaces.ts +++ b/packages/core/src/components/router/utils/interfaces.ts @@ -6,6 +6,11 @@ export interface NavOutlet { getContainerEl(): HTMLElement | null; } +export interface RouteRedirect { + path: string[]; + to: string[]; +} + export interface RouteWrite { changed: boolean; markVisible?: () => void|Promise; diff --git a/packages/core/src/components/router/utils/matching.ts b/packages/core/src/components/router/utils/matching.ts index 39e0480cc5..1ee5169684 100644 --- a/packages/core/src/components/router/utils/matching.ts +++ b/packages/core/src/components/router/utils/matching.ts @@ -1,4 +1,29 @@ -import { RouteChain, RouteID } from './interfaces'; +import { RouteChain, RouteID, RouteRedirect } from './interfaces'; + + +export function matchesRedirect(input: string[], route: RouteRedirect): boolean { + const {path} = route; + if (path.length !== input.length) { + return false; + } + + for (let i = 0; i < path.length; i++) { + if (path[i] !== input[i]) { + return false; + } + } + return true; +} + +export function routeRedirect(path: string[], routes: RouteRedirect[]): RouteRedirect|null { + for (const route of routes) { + if (matchesRedirect(path, route)) { + return route; + } + } + return null; +} + export function matchesIDs(ids: string[], chain: RouteChain): number { const len = Math.min(ids.length, chain.length); diff --git a/packages/core/src/components/router/utils/parser.ts b/packages/core/src/components/router/utils/parser.ts index 2c2a500438..16d6012706 100644 --- a/packages/core/src/components/router/utils/parser.ts +++ b/packages/core/src/components/router/utils/parser.ts @@ -1,10 +1,24 @@ -import { RouteChain, RouteNode, RouteTree } from './interfaces'; +import { RouteChain, RouteNode, RouteRedirect, RouteTree } from './interfaces'; import { parsePath } from './path'; +export function readRedirects(root: Element): RouteRedirect[] { + return (Array.from(root.children) as HTMLIonRouteElement[]) + .filter(el => el.tagName === 'ION-ROUTE' && el.redirectTo) + .map(el => { + if (el.component) { + throw new Error('Can\'t mix the component and redirectTo attribute in the same ion-route'); + } + return { + path: parsePath(readProp(el, 'path')), + to: parsePath(readProp(el, 'redirectTo')) + }; + }); +} + export function readRoutes(root: Element): RouteTree { return (Array.from(root.children) as HTMLIonRouteElement[]) - .filter(el => el.tagName === 'ION-ROUTE') + .filter(el => el.tagName === 'ION-ROUTE' && el.component) .map(el => { const path = parsePath(readProp(el, 'path')); if (path.includes(':id')) {