From c905ba46111e167d8555bfcb1a4f4bcb053a26be Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Mon, 30 Apr 2018 14:18:06 +0200 Subject: [PATCH] fix(router): initial load waits until outlet attaches --- core/src/components.d.ts | 3 + core/src/components/nav/nav.tsx | 2 + core/src/components/nav/readme.md | 3 + core/src/components/router-outlet/readme.md | 3 + .../components/router-outlet/route-outlet.tsx | 3 + core/src/components/router/router.tsx | 5 +- core/src/components/router/test/path.spec.tsx | 76 ++++++++++++++++++- core/src/components/tabs/readme.md | 3 + core/src/components/tabs/tabs.tsx | 3 + 9 files changed, 97 insertions(+), 4 deletions(-) diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 420a489580a..bfa62a86f9c 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -3860,6 +3860,7 @@ declare global { 'delegate'?: FrameworkDelegate; 'onIonNavDidChange'?: (event: CustomEvent) => void; 'onIonNavWillChange'?: (event: CustomEvent) => void; + 'onIonNavWillLoad'?: (event: CustomEvent) => void; 'root'?: NavComponent; 'rootParams'?: ComponentProps; 'swipeBackEnabled'?: boolean; @@ -5120,6 +5121,7 @@ declare global { 'delegate'?: FrameworkDelegate; 'onIonNavDidChange'?: (event: CustomEvent) => void; 'onIonNavWillChange'?: (event: CustomEvent) => void; + 'onIonNavWillLoad'?: (event: CustomEvent) => void; } } } @@ -6507,6 +6509,7 @@ declare global { 'onIonChange'?: (event: CustomEvent<{tab: HTMLIonTabElement}>) => void; 'onIonNavDidChange'?: (event: CustomEvent) => void; 'onIonNavWillChange'?: (event: CustomEvent) => void; + 'onIonNavWillLoad'?: (event: CustomEvent) => void; 'scrollable'?: boolean; /** * If true, the tabbar diff --git a/core/src/components/nav/nav.tsx b/core/src/components/nav/nav.tsx index 52c4da88027..7b912a4a080 100644 --- a/core/src/components/nav/nav.tsx +++ b/core/src/components/nav/nav.tsx @@ -43,6 +43,7 @@ export class Nav implements NavOutlet { } } + @Event() ionNavWillLoad!: EventEmitter; @Event() ionNavWillChange!: EventEmitter; @Event() ionNavDidChange!: EventEmitter; @@ -54,6 +55,7 @@ export class Nav implements NavOutlet { if (this.animated === undefined) { this.animated = this.config.getBoolean('animate', true); } + this.ionNavWillLoad.emit(); } componentDidLoad() { diff --git a/core/src/components/nav/readme.md b/core/src/components/nav/readme.md index 520fc941226..3fc55f32932 100644 --- a/core/src/components/nav/readme.md +++ b/core/src/components/nav/readme.md @@ -67,6 +67,9 @@ boolean #### ionNavWillChange +#### ionNavWillLoad + + ## Methods #### canGoBack() diff --git a/core/src/components/router-outlet/readme.md b/core/src/components/router-outlet/readme.md index dc7b2f351f8..6a62e81b183 100644 --- a/core/src/components/router-outlet/readme.md +++ b/core/src/components/router-outlet/readme.md @@ -47,6 +47,9 @@ boolean #### ionNavWillChange +#### ionNavWillLoad + + ## Methods #### commit() diff --git a/core/src/components/router-outlet/route-outlet.tsx b/core/src/components/router-outlet/route-outlet.tsx index c589ebb04c7..d4522345eaa 100644 --- a/core/src/components/router-outlet/route-outlet.tsx +++ b/core/src/components/router-outlet/route-outlet.tsx @@ -25,6 +25,7 @@ export class RouterOutlet implements NavOutlet { @Prop() animationBuilder?: AnimationBuilder; @Prop() delegate?: FrameworkDelegate; + @Event() ionNavWillLoad!: EventEmitter; @Event() ionNavWillChange!: EventEmitter; @Event() ionNavDidChange!: EventEmitter; @@ -32,6 +33,8 @@ export class RouterOutlet implements NavOutlet { if (this.animated === undefined) { this.animated = this.config.getBoolean('animate', true); } + + this.ionNavWillLoad.emit(); } componentDidUnload() { diff --git a/core/src/components/router/router.tsx b/core/src/components/router/router.tsx index 0c6659894b2..66f9dfe7f87 100644 --- a/core/src/components/router/router.tsx +++ b/core/src/components/router/router.tsx @@ -1,6 +1,6 @@ import { Component, Element, Event, EventEmitter, Listen, Method, Prop } from '@stencil/core'; import { Config, QueueController } from '../../interface'; -import { readNavState, writeNavState } from './utils/dom'; +import { readNavState, waitUntilNavNode, writeNavState } from './utils/dom'; import { RouteChain, RouteRedirect, RouterDirection, RouterEventDetail } from './utils/interface'; import { routeRedirect, routerIDsToChain, routerPathToChain } from './utils/matching'; import { flattenRouterTree, readRedirects, readRoutes } from './utils/parser'; @@ -55,6 +55,8 @@ export class Router { async componentWillLoad() { console.debug('[ion-router] router will load'); + await waitUntilNavNode(this.win); + console.debug('[ion-router] found nav'); const tree = readRoutes(this.el); this.routes = flattenRouterTree(tree); @@ -68,7 +70,6 @@ export class Router { componentDidLoad() { this.init = true; - console.debug('[ion-router] router did load'); } diff --git a/core/src/components/router/test/path.spec.tsx b/core/src/components/router/test/path.spec.tsx index fcadc045091..ae20a3ea3ee 100644 --- a/core/src/components/router/test/path.spec.tsx +++ b/core/src/components/router/test/path.spec.tsx @@ -1,5 +1,5 @@ -import { RouteChain } from '../utils/interface'; -import { chainToPath, generatePath, parsePath, readPath } from '../utils/path'; +import { RouteChain, RouterDirection } from '../utils/interface'; +import { chainToPath, generatePath, parsePath, readPath, writePath } from '../utils/path'; describe('parseURL', () => { it('should parse empty path', () => { @@ -176,6 +176,78 @@ describe('readPath', () => { }); }); +describe('writePath', () => { + it('should write root path (no hash)', () => { + const history = mockHistory(); + writePath(history, '', false, [''], RouterDirection.Forward, 123); + expect(history.pushState).toHaveBeenCalledWith(123, '', '/'); + + writePath(history, '', false, ['schedule'], RouterDirection.Forward, 123); + expect(history.pushState).toHaveBeenCalledWith(123, '', '/schedule'); + + writePath(history, '/', false, [''], RouterDirection.Forward, 123); + expect(history.pushState).toHaveBeenCalledWith(123, '', '/'); + + writePath(history, '/', false, ['to', 'schedule'], RouterDirection.Forward, 123); + expect(history.pushState).toHaveBeenCalledWith(123, '', '/to/schedule'); + }); + + + it('should write non root path (no hash)', () => { + const history = mockHistory(); + writePath(history, '/path', false, [''], RouterDirection.Forward, 2); + expect(history.pushState).toHaveBeenCalledWith(2, '', '/path'); + + writePath(history, '/path', false, ['to', 'page'], RouterDirection.Forward, 2); + expect(history.pushState).toHaveBeenCalledWith(2, '', '/path/to/page'); + + writePath(history, 'path/to', false, ['second', 'page'], RouterDirection.Forward, 2); + expect(history.pushState).toHaveBeenCalledWith(2, '', '/path/to/second/page'); + + writePath(history, '/path/to/', false, ['second', 'page'], RouterDirection.Forward, 2); + expect(history.pushState).toHaveBeenCalledWith(2, '', '/path/to/second/page'); + }); + + it('should write root path (no hash)', () => { + const history = mockHistory(); + writePath(history, '', true, [''], RouterDirection.Forward, 123); + expect(history.pushState).toHaveBeenCalledWith(123, '', '#/'); + + writePath(history, '', true, ['schedule'], RouterDirection.Forward, 123); + expect(history.pushState).toHaveBeenCalledWith(123, '', '#/schedule'); + + writePath(history, '/', true, [''], RouterDirection.Forward, 123); + expect(history.pushState).toHaveBeenCalledWith(123, '', '#/'); + + writePath(history, '/', true, ['to', 'schedule'], RouterDirection.Forward, 123); + expect(history.pushState).toHaveBeenCalledWith(123, '', '#/to/schedule'); + }); + + it('should write non root path (no hash)', () => { + const history = mockHistory(); + writePath(history, '/path', true, [''], RouterDirection.Forward, 123); + expect(history.pushState).toHaveBeenCalledWith(123, '', '#/path'); + + writePath(history, '/path', true, ['to', 'page'], RouterDirection.Forward, 123); + expect(history.pushState).toHaveBeenCalledWith(123, '', '#/path/to/page'); + + writePath(history, 'path/to', true, ['second', 'page'], RouterDirection.Forward, 123); + expect(history.pushState).toHaveBeenCalledWith(123, '', '#/path/to/second/page'); + + writePath(history, '/path/to/', true, ['second', 'page'], RouterDirection.Forward, 123); + expect(history.pushState).toHaveBeenCalledWith(123, '', '#/path/to/second/page'); + }); +}); + +function mockHistory(): History { + return { + replaceState: jest.fn(), + pushState: jest.fn(), + length: 0, + } as any; +} + + function mockLocation(pathname: string, hash: string): Location { return { pathname, diff --git a/core/src/components/tabs/readme.md b/core/src/components/tabs/readme.md index 4a4f199c254..eb7dc2f6be9 100644 --- a/core/src/components/tabs/readme.md +++ b/core/src/components/tabs/readme.md @@ -233,6 +233,9 @@ Emitted when the tab changes. #### ionNavWillChange +#### ionNavWillLoad + + ## Methods #### getRouteId() diff --git a/core/src/components/tabs/tabs.tsx b/core/src/components/tabs/tabs.tsx index 0a58fa7f627..026ee1390bf 100644 --- a/core/src/components/tabs/tabs.tsx +++ b/core/src/components/tabs/tabs.tsx @@ -70,6 +70,7 @@ export class Tabs implements NavOutlet { * Emitted when the tab changes. */ @Event() ionChange!: EventEmitter<{tab: HTMLIonTabElement}>; + @Event() ionNavWillLoad!: EventEmitter; @Event() ionNavWillChange!: EventEmitter; @Event() ionNavDidChange!: EventEmitter; @@ -81,6 +82,8 @@ export class Tabs implements NavOutlet { this.loadConfig('tabbarLayout', 'bottom'); this.loadConfig('tabbarLayout', 'icon-top'); this.loadConfig('tabbarHighlight', false); + + this.ionNavWillLoad.emit(); } async componentDidLoad() {