From d613411287893a2cb943462bbc428701eb4e3611 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Mon, 30 Apr 2018 18:49:22 +0200 Subject: [PATCH] feat(router): add willChange event --- core/src/components.d.ts | 3 +- core/src/components/nav/nav.tsx | 5 +- core/src/components/router/router.tsx | 67 +++++++++++++------ core/src/components/router/utils/dom.ts | 17 ++--- core/src/components/router/utils/interface.ts | 3 +- 5 files changed, 62 insertions(+), 33 deletions(-) diff --git a/core/src/components.d.ts b/core/src/components.d.ts index bfa62a86f9..7d0fc43071 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -5163,7 +5163,8 @@ declare global { } namespace JSXElements { export interface IonRouterAttributes extends HTMLAttributes { - 'onIonRouteChanged'?: (event: CustomEvent) => void; + 'onIonRouteDidChange'?: (event: CustomEvent) => void; + 'onIonRouteWillChange'?: (event: CustomEvent) => void; /** * By default `ion-router` will match the routes at the root path ("/"). That can be changed when T */ diff --git a/core/src/components/nav/nav.tsx b/core/src/components/nav/nav.tsx index 7b912a4a08..75ce834de6 100644 --- a/core/src/components/nav/nav.tsx +++ b/core/src/components/nav/nav.tsx @@ -172,7 +172,10 @@ export class Nav implements NavOutlet { setRouteId(id: string, params: any, direction: number): Promise { const active = this.getActive(); if (matches(active, id, params)) { - return Promise.resolve({changed: false, element: active.element}); + return Promise.resolve({ + changed: false, + element: active.element + }); } const viewController = this.views.find(v => matches(v, id, params)); diff --git a/core/src/components/router/router.tsx b/core/src/components/router/router.tsx index 66f9dfe7f8..fd84831faf 100644 --- a/core/src/components/router/router.tsx +++ b/core/src/components/router/router.tsx @@ -25,7 +25,6 @@ export class Router { @Prop({ context: 'config' }) config!: Config; @Prop({ context: 'queue' }) queue!: QueueController; @Prop({ context: 'window' }) win!: Window; - @Prop({ context: 'isServer' }) isServer!: boolean; /** * By default `ion-router` will match the routes at the root path ("/"). @@ -51,7 +50,8 @@ export class Router { */ @Prop() useHash = true; - @Event() ionRouteChanged!: EventEmitter; + @Event() ionRouteWillChange!: EventEmitter; + @Event() ionRouteDidChange!: EventEmitter; async componentWillLoad() { console.debug('[ion-router] router will load'); @@ -150,11 +150,8 @@ export class Router { console.debug('[ion-router] nav changed -> update URL', ids, path); this.setPath(path, direction); - if (outlet) { - console.debug('[ion-router] updating nested outlet', outlet); - await this.writeNavState(outlet, chain, RouterDirection.None, ids.length); - } - this.emitRouteChange(path, null); + + await this.writeNavState(outlet, chain, RouterDirection.None, path, null, ids.length); return true; } @@ -175,6 +172,8 @@ export class Router { console.error('[ion-router] URL is not part of the routing set'); return false; } + + // lookup redirect rule const redirect = routeRedirect(path, this.redirects); let redirectFrom: string[]|null = null; if (redirect) { @@ -182,21 +181,38 @@ export class Router { redirectFrom = redirect.from; path = redirect.to!; } + + // lookup route chain const chain = routerPathToChain(path, this.routes); - const changed = await this.writeNavState(this.win.document.body, chain, direction); - if (changed) { - this.emitRouteChange(path, redirectFrom); + if (!chain) { + console.error('[ion-router] the path does not match any route'); + return false; } - return changed; + + // write DOM give + return this.writeNavState(this.win.document.body, chain, direction, path, redirectFrom); } - private async writeNavState(node: any, chain: RouteChain | null, direction: RouterDirection, index = 0): Promise { + private async writeNavState( + node: HTMLElement|undefined, chain: RouteChain, direction: RouterDirection, + path: string[], redirectFrom: string[] | null, + index = 0 + ): Promise { if (this.busy) { return false; } this.busy = true; + + // generate route event and emit will change + const event = this.routeChangeEvent(path, redirectFrom); + this.ionRouteWillChange.emit(event); + const changed = await writeNavState(node, chain, direction, index); this.busy = false; + + // emit did change + this.emitRouteDidChange(event, changed); + return changed; } @@ -209,16 +225,23 @@ export class Router { return readPath(this.win.location, this.root, this.useHash); } - private emitRouteChange(path: string[], redirectPath: string[]|null) { - console.debug('[ion-router] route changed', path); - const from = this.previousPath; - const redirectedFrom = redirectPath ? generatePath(redirectPath) : null; - const to = generatePath(path); - this.previousPath = to; - this.ionRouteChanged.emit({ - from, - redirectedFrom, - to: to + private emitRouteDidChange(event: RouterEventDetail, changed: boolean) { + console.debug('[ion-router] route changed', event.to); + this.previousPath = event.to; + this.ionRouteDidChange.emit({ + ...event, + changed }); } + + private routeChangeEvent(path: string[], redirectFromPath: string[]|null) { + const from = this.previousPath; + const to = generatePath(path); + const redirectedFrom = redirectFromPath ? generatePath(redirectFromPath) : null; + return { + from, + redirectedFrom, + to, + }; + } } diff --git a/core/src/components/router/utils/dom.ts b/core/src/components/router/utils/dom.ts index b480619021..925d65c6cf 100644 --- a/core/src/components/router/utils/dom.ts +++ b/core/src/components/router/utils/dom.ts @@ -1,12 +1,12 @@ -import { NavOutlet, NavOutletElement, RouteChain, RouteID, RouterDirection } from './interface'; +import { NavOutletElement, RouteChain, RouteID, RouterDirection } from './interface'; -export async function writeNavState(root: HTMLElement|undefined, chain: RouteChain|null, direction: RouterDirection, index: number, changed = false): Promise { +export async function writeNavState(root: HTMLElement | undefined, chain: RouteChain, direction: RouterDirection, index: number, changed = false): Promise { try { // find next navigation outlet in the DOM const outlet = searchNavNode(root); // make sure we can continue interating the DOM, otherwise abort - if (!chain || index >= chain.length || !outlet) { + if (index >= chain.length || !outlet) { return changed; } await outlet.componentOnReady(); @@ -38,8 +38,8 @@ export async function writeNavState(root: HTMLElement|undefined, chain: RouteCha export function readNavState(root: HTMLElement | undefined) { const ids: RouteID[] = []; - let outlet: NavOutlet|null; - let node: HTMLElement|undefined = root; + let outlet: NavOutletElement | undefined; + let node: HTMLElement | undefined = root; while (true) { outlet = searchNavNode(node); if (outlet) { @@ -69,12 +69,13 @@ export function waitUntilNavNode(win: Window) { const QUERY = ':not([no-router]) ion-nav, :not([no-router]) ion-tabs, :not([no-router]) ion-router-outlet'; -function searchNavNode(root: HTMLElement|undefined): NavOutletElement|null { +function searchNavNode(root: HTMLElement | undefined): NavOutletElement | undefined { if (!root) { - return null; + return undefined; } if (root.matches(QUERY)) { return root as NavOutletElement; } - return root.querySelector(QUERY); + const outlet = root.querySelector(QUERY); + return outlet ? outlet : undefined; } diff --git a/core/src/components/router/utils/interface.ts b/core/src/components/router/utils/interface.ts index f91d1b2755..00f74de47b 100644 --- a/core/src/components/router/utils/interface.ts +++ b/core/src/components/router/utils/interface.ts @@ -8,6 +8,7 @@ export interface RouterEventDetail { from: string|null; redirectedFrom: string|null; to: string; + changed?: boolean; } export const enum RouterDirection { @@ -29,7 +30,7 @@ export interface RouteWrite { export interface RouteID { id: string; - element?: HTMLElement|undefined; + element: HTMLElement | undefined; params?: any; }