refactor(nav): implement nav integration with external router (angular to start)

This commit is contained in:
Dan Bucholtz
2018-02-13 00:02:48 -06:00
committed by GitHub
parent 3fa8623cfe
commit edeb1324c9
91 changed files with 2387 additions and 3894 deletions

View File

@ -3,10 +3,10 @@ import {
Directive,
ElementRef,
Injector,
Optional,
Type,
} from '@angular/core';
import { FrameworkDelegate } from '@ionic/core';
import { AngularComponentMounter, AngularEscapeHatch } from '..';
@ -22,10 +22,10 @@ export class IonNav implements FrameworkDelegate {
public elementRef: ElementRef,
protected angularComponentMounter: AngularComponentMounter,
protected cfr: ComponentFactoryResolver,
protected injector: Injector) {
protected injector: Injector
) {
this.elementRef.nativeElement.delegate = this;
}
attachViewToDom(elementOrContainerToMountTo: HTMLIonNavElement,

View File

@ -22,16 +22,19 @@ import {
Router
} from '@angular/router';
import { NavResult, RouterDelegate } from '@ionic/core';
import { OutletInjector } from './outlet-injector';
import { RouteEventHandler } from './route-event-handler';
import { AngularComponentMounter, AngularEscapeHatch } from '..';
import { OutletInjector } from './outlet-injector';
let id = 0;
@Directive({
selector: 'ion-nav',
})
export class RouterOutlet implements OnDestroy, OnInit {
export class RouterOutlet implements OnDestroy, OnInit, RouterDelegate {
public name: string;
public activationStatus = NOT_ACTIVATED;
@ -53,12 +56,24 @@ export class RouterOutlet implements OnDestroy, OnInit {
protected parentContexts: ChildrenOutletContexts,
protected cfr: ComponentFactoryResolver,
protected injector: Injector,
protected router: Router,
private routeEventHandler: RouteEventHandler,
@Attribute('name') name: string) {
(this.elementRef.nativeElement as HTMLIonNavElement).routerDelegate = this;
this.name = name || PRIMARY_OUTLET;
parentContexts.onChildOutletCreated(this.name, this as any);
}
pushUrlState(urlSegment: string): Promise<any> {
return this.router.navigateByUrl(urlSegment);
}
popUrlState(): Promise<any> {
window.history.back();
return Promise.resolve();
}
ngOnDestroy(): void {
console.debug(`Nav ${this.id} ngOnDestroy`);
this.parentContexts.onChildOutletDestroyed(this.name);
@ -93,6 +108,7 @@ export class RouterOutlet implements OnDestroy, OnInit {
}
activateWith(activatedRoute: ActivatedRoute, cfr: ComponentFactoryResolver): Promise<void> {
this.routeEventHandler.externalNavStart();
if (this.activationStatus !== NOT_ACTIVATED) {
return Promise.resolve();
}
@ -105,7 +121,8 @@ export class RouterOutlet implements OnDestroy, OnInit {
const childContexts = this.parentContexts.getOrCreateContext(this.name).children;
const injector = new OutletInjector(activatedRoute, childContexts, this.location.injector);
return activateRoute(this.elementRef.nativeElement, component, cfr, injector).then(() => {
const isTopLevel = !hasChildComponent(activatedRoute);
return activateRoute(this.elementRef.nativeElement, component, cfr, injector, isTopLevel).then(() => {
this.changeDetector.markForCheck();
this.activateEvents.emit(null);
this.activationStatus = ACTIVATED;
@ -114,16 +131,16 @@ export class RouterOutlet implements OnDestroy, OnInit {
}
export function activateRoute(navElement: HTMLIonNavElement,
component: Type<any>, cfr: ComponentFactoryResolver, injector: Injector): Promise<void> {
component: Type<any>, cfr: ComponentFactoryResolver, injector: Injector, isTopLevel: boolean): Promise<void> {
return navElement.componentOnReady().then(() => {
// check if the nav has an `<ion-tab>` as a parent
if (isParentTab(navElement)) {
// check if the tab is selected
return updateTab(navElement, component, cfr, injector);
return updateTab(navElement, component, cfr, injector, isTopLevel);
} else {
return updateNav(navElement, component, cfr, injector);
return updateNav(navElement, component, cfr, injector, isTopLevel);
}
});
}
@ -148,42 +165,47 @@ function getSelected(tabsElement: HTMLIonTabsElement) {
}
function updateTab(navElement: HTMLIonNavElement,
component: Type<any>, cfr: ComponentFactoryResolver, injector: Injector) {
component: Type<any>, cfr: ComponentFactoryResolver, injector: Injector, isTopLevel: boolean) {
const tab = navElement.parentElement as HTMLIonTabElement;
// tab.externalNav = true;
// (tab.parentElement as HTMLIonTabsElement).externalInitialize = true;
// yeah yeah, I know this is kind of ugly but oh well, I know the internal structure of <ion-tabs>
const tabs = tab.parentElement.parentElement as HTMLIonTabsElement;
// tabs.externalInitialize = true;
return isTabSelected(tabs, tab).then((isSelected: boolean) => {
if (!isSelected) {
const promise = updateNav(navElement, component, cfr, injector, isTopLevel);
(window as any).externalNavPromise = promise
// okay, the tab is not selected, so we need to do a "switch" transition
// basically, we should update the nav, and then swap the tabs
return updateNav(navElement, component, cfr, injector).then(() => {
return promise.then(() => {
return tabs.select(tab);
});
}
// okay cool, the tab is already selected, so we want to see a transition
return updateNav(navElement, component, cfr, injector);
return updateNav(navElement, component, cfr, injector, isTopLevel);
})
}
function updateNav(navElement: HTMLIonNavElement,
component: Type<any>, cfr: ComponentFactoryResolver, injector: Injector) {
component: Type<any>, cfr: ComponentFactoryResolver, injector: Injector, isTopLevel: boolean): Promise<NavResult> {
const escapeHatch = getEscapeHatch(cfr, injector);
// check if the component is the top view
const activeViews = navElement.getViews();
if (activeViews.length === 0) {
// there isn't a view in the stack, so push one
return navElement.push(component, {}, {}, {
cfr,
injector
});
return navElement.setRoot(component, {}, {}, escapeHatch);
}
const currentView = activeViews[activeViews.length - 1];
if (currentView.component === component) {
// the top view is already the component being activated, so there is no change needed
return Promise.resolve();
return Promise.resolve(null);
}
// check if the component is the previous view, if so, pop back to it
@ -192,7 +214,7 @@ function updateNav(navElement: HTMLIonNavElement,
const previousView = activeViews[activeViews.length - 2];
if (previousView.component === component) {
// cool, we match the previous view, so pop it
return navElement.pop();
return navElement.pop(null, escapeHatch);
}
}
@ -200,17 +222,33 @@ function updateNav(navElement: HTMLIonNavElement,
for (const view of activeViews) {
if (view.component === component) {
// cool, we found the match, pop back to that bad boy
return navElement.popTo(view);
return navElement.popTo(view, null, escapeHatch);
}
}
// since it's none of those things, we should probably just push that bad boy and call it a day
return navElement.push(component, {}, {}, {
cfr,
injector
});
// it's the top level nav, and it's not one of those other behaviors, so do a push so the user gets a chill animation
return navElement.push(component, {}, { animate: isTopLevel }, escapeHatch);
}
export const NOT_ACTIVATED = 0;
export const ACTIVATION_IN_PROGRESS = 1;
export const ACTIVATED = 2;
export function hasChildComponent(activatedRoute: ActivatedRoute): boolean {
// don't worry about recursion for now, that's a future problem that may or may not manifest itself
for (const childRoute of activatedRoute.children) {
if (childRoute.component) {
return true;
}
}
return false;
}
export function getEscapeHatch(cfr: ComponentFactoryResolver, injector: Injector): AngularEscapeHatch {
return {
cfr,
injector,
fromExternalRouter: true,
url: location.pathname
};
}

View File

@ -0,0 +1,27 @@
import { Injectable } from '@angular/core';
import {
Event,
NavigationEnd,
NavigationStart,
Router
} from '@angular/router';
let initialized = false;
@Injectable()
export class RouteEventHandler {
constructor(private router: Router) {
(window as any).externalNav = false;
router.events.subscribe((event: Event) => {
if (event instanceof NavigationEnd) {
(window as any).externalNav = false;
}
});
}
externalNavStart() {
(window as any).externalNav = true;
}
}

View File

@ -30,6 +30,7 @@ import { IonicAngularModule } from '../module';
import { PushPopOutletContexts } from './push-pop-outlet-contexts';
import { CustomRouter } from './router';
import { RouteEventHandler } from './route-event-handler';
import { RouterOutlet } from './outlet';
import { flatten } from './router-utils';
@ -62,6 +63,7 @@ export class IonicRouterModule {
[UrlHandlingStrategy, new Optional()], [RouteReuseStrategy, new Optional()]
]
},
RouteEventHandler
]
};
}

View File

@ -20,8 +20,6 @@ export class CustomRouter extends Router {
protected activateRoutes(state: Observable<{appliedUrl: string, state: RouterState, shouldActivate: boolean}>, storedState: RouterState,
storedUrl: UrlTree, id: number, url: UrlTree, rawUrl: UrlTree, skipLocationChange: boolean, replaceUrl: boolean, resolvePromise: any, rejectPromise: any) {
console.log('custom activateRoutes!!!!1');
// applied the new router state
// this operation has side effects
let navigationIsSuccessful: boolean;

View File

@ -6,7 +6,7 @@ import {
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { FrameworkMountingData } from '@ionic/core';
import { EscapeHatch, FrameworkMountingData} from '@ionic/core';
export interface AngularMountingData extends FrameworkMountingData {
componentFactory?: ComponentFactory<any>;
@ -16,7 +16,7 @@ export interface AngularMountingData extends FrameworkMountingData {
angularHostElement?: HTMLElement;
}
export interface AngularEscapeHatch {
export interface AngularEscapeHatch extends EscapeHatch {
activatedRoute?: ActivatedRoute;
cfr?: ComponentFactoryResolver;
injector?: Injector;

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,7 @@ import {
FrameworkDelegate,
PickerColumn,
PickerOptions,
RouterDelegate,
} from './index';
import {
AlertButton,
@ -1837,6 +1838,7 @@ declare global {
lazy?: boolean;
mode?: string;
root?: any;
routerDelegate?: RouterDelegate;
swipeBackEnabled?: boolean;
useUrls?: boolean;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
import { Component, Element, Listen } from '@stencil/core';
import { NavResult } from '../..';
@Component({
tag: 'ion-nav-pop',
})
@ -8,12 +9,12 @@ export class NavPop {
@Element() element: HTMLElement;
@Listen('child:click')
pop() {
pop(): Promise<NavResult> {
const nav = this.element.closest('ion-nav') as HTMLIonNavElement;
if (nav) {
return nav.pop();
}
return Promise.resolve();
return Promise.resolve(null);
}
render() {

View File

@ -36,6 +36,7 @@ export interface PublicNav {
name?: string;
element?: HTMLElement;
parent?: PublicNav;
initialized?: boolean;
}
export interface NavOptions {
@ -71,9 +72,9 @@ export interface TransitionInstruction {
nav?: Nav;
delegate?: FrameworkDelegate;
animation?: Animation;
escapeHatch?: any;
escapeHatch?: EscapeHatch;
method?: string;
mountingData?: any;
mountingData?: FrameworkMountingData;
}
export interface NavResult {
@ -86,6 +87,18 @@ export interface ComponentDataPair {
data: any;
}
export interface ExternalNavData {
url: string;
method: string;
resolve: Function;
reject: Function;
}
export interface EscapeHatch {
fromExternalRouter?: boolean;
url?: string;
}
export interface Transition extends Animation {
enteringView?: ViewController;
leavingView?: ViewController;

View File

@ -29,22 +29,12 @@ export let VIEW_ID_START = 2000;
let transitionIds = 0;
const activeTransitions = new Map<number, any>();
let portalZindex = 9999;
export function isViewController(object: any): boolean {
return !!(object && object.didLoad && object.willUnload);
}
export function setZIndex(nav: Nav, enteringView: ViewController, leavingView: ViewController, direction: string) {
if (enteringView) {
if (nav.isPortal) {
if (direction === DIRECTION_FORWARD) {
// TODO - fix typing
updateZIndex(enteringView, (nav as any).zIndexOffset + portalZindex);
}
portalZindex++;
return;
}
leavingView = leavingView || nav.getPrevious(enteringView) as ViewController;

View File

@ -5,12 +5,16 @@ import {
AnimationOptions,
ComponentDataPair,
Config,
EscapeHatch,
ExternalNavData,
FrameworkDelegate,
NavOptions,
NavOutlet,
NavResult,
PublicNav,
PublicViewController,
RouterDelegate,
RouterEntries,
Transition,
TransitionInstruction,
} from '../../index';
@ -40,12 +44,14 @@ import {
} from './nav-utils';
import { DomFrameworkDelegate } from '../../utils/dom-framework-delegate';
import { DomRouterDelegate } from '../../utils/dom-router-delegate';
import {
assert,
focusOutActiveElement,
isDef,
isNumber,
normalizeUrl,
} from '../../utils/helpers';
@ -53,8 +59,8 @@ import { buildIOSTransition } from './transitions/transition.ios';
import { buildMdTransition } from './transitions/transition.md';
import { GestureDetail } from '../gesture/gesture';
const queueMap = new Map<number, TransitionInstruction[]>();
const urlMap = new Map<string, TransitionInstruction>();
const transitionQueue = new Map<number, TransitionInstruction[]>();
const allTransitionsCompleteHandlerQueue = new Map<number, Function[]>();
/* it is very important to keep this class in sync with ./nav-interface interface */
@Component({
@ -67,21 +73,23 @@ export class Nav implements PublicNav, NavOutlet {
@Event() navInit: EventEmitter<NavEventDetail>;
@Event() ionNavChanged: EventEmitter<NavEventDetail>;
navId: number;
init = false;
parent: Nav;
useRouter = false;
navId = getNextNavId();
routes: RouterEntries = [];
parent: Nav = null;
views: ViewController[] = [];
transitioning?: boolean;
destroyed?: boolean;
transitionId?: number;
isViewInitialized?: boolean;
isPortal: boolean;
transitioning = false;
destroyed = false;
transitionId = NOT_TRANSITIONING_TRANSITION_ID;
initialized = false;
sbTrns: any; // TODO Transition
childNavs?: Nav[];
urlExternalNavMap = new Map<string, ExternalNavData>();
@Prop() mode: string;
@Prop() root: any;
@Prop() delegate: FrameworkDelegate;
@Prop() routerDelegate: RouterDelegate;
@Prop() useUrls = false;
@Prop({ context: 'config' }) config: Config;
@Prop({ connect: 'ion-animation-controller' }) animationCtrl: AnimationController;
@ -93,20 +101,15 @@ export class Nav implements PublicNav, NavOutlet {
}
componentDidLoad() {
if (this.init) {
return;
}
this.init = true;
if (!this.lazy) {
componentDidLoadImpl(this);
}
return componentDidLoadImpl(this);
}
@Watch('root')
updateRootComponent(): any {
if (this.init) {
updateRootComponent(): Promise<NavResult> {
if (this.initialized) {
return this.setRoot(this.root);
}
return Promise.resolve(null);
}
@Method()
@ -115,52 +118,52 @@ export class Nav implements PublicNav, NavOutlet {
}
@Method()
push(component: any, data?: any, opts?: NavOptions, escapeHatch: any = {}): Promise<any> {
push(component: any, data?: any, opts?: NavOptions, escapeHatch: EscapeHatch = getDefaultEscapeHatch()): Promise<NavResult> {
return pushImpl(this, component, data, opts, escapeHatch);
}
@Method()
pop(opts?: NavOptions, escapeHatch: any = {}): Promise<any> {
pop(opts?: NavOptions, escapeHatch: EscapeHatch = getDefaultEscapeHatch()): Promise<NavResult> {
return popImpl(this, opts, escapeHatch);
}
@Method()
setRoot(component: any, data?: any, opts?: NavOptions, escapeHatch: any = {}): Promise<any> {
setRoot(component: any, data?: any, opts?: NavOptions, escapeHatch: EscapeHatch = getDefaultEscapeHatch()): Promise<NavResult> {
return setRootImpl(this, component, data, opts, escapeHatch);
}
@Method()
insert(insertIndex: number, page: any, params?: any, opts?: NavOptions, escapeHatch: any = {}): Promise<any> {
insert(insertIndex: number, page: any, params?: any, opts?: NavOptions, escapeHatch: EscapeHatch = getDefaultEscapeHatch()): Promise<NavResult> {
return insertImpl(this, insertIndex, page, params, opts, escapeHatch);
}
@Method()
insertPages(insertIndex: number, insertPages: any[], opts?: NavOptions, escapeHatch: any = {}): Promise<any> {
insertPages(insertIndex: number, insertPages: any[], opts?: NavOptions, escapeHatch: EscapeHatch = getDefaultEscapeHatch()): Promise<NavResult> {
return insertPagesImpl(this, insertIndex, insertPages, opts, escapeHatch);
}
@Method()
popToRoot(opts?: NavOptions, escapeHatch: any = {}): Promise<any> {
popToRoot(opts?: NavOptions, escapeHatch: EscapeHatch = getDefaultEscapeHatch()): Promise<NavResult> {
return popToRootImpl(this, opts, escapeHatch);
}
@Method()
popTo(indexOrViewCtrl: any, opts?: NavOptions, escapeHatch: any = {}): Promise<any> {
popTo(indexOrViewCtrl: any, opts?: NavOptions, escapeHatch: EscapeHatch = getDefaultEscapeHatch()): Promise<NavResult> {
return popToImpl(this, indexOrViewCtrl, opts, escapeHatch);
}
@Method()
removeIndex(startIndex: number, removeCount?: number, opts?: NavOptions, escapeHatch: any = {}): Promise<any> {
removeIndex(startIndex: number, removeCount?: number, opts?: NavOptions, escapeHatch: EscapeHatch = getDefaultEscapeHatch()): Promise<NavResult> {
return removeImpl(this, startIndex, removeCount, opts, escapeHatch);
}
@Method()
removeView(viewController: PublicViewController, opts?: NavOptions, escapeHatch: any = {}): Promise<any> {
removeView(viewController: PublicViewController, opts?: NavOptions, escapeHatch: EscapeHatch = getDefaultEscapeHatch()): Promise<NavResult> {
return removeViewImpl(this, viewController, opts, escapeHatch);
}
@Method()
setPages(componentDataPairs: ComponentDataPair[], opts?: NavOptions, escapeHatch: any = {}): Promise<any> {
setPages(componentDataPairs: ComponentDataPair[], opts?: NavOptions, escapeHatch: EscapeHatch = getDefaultEscapeHatch()): Promise<NavResult> {
return setPagesImpl(this, componentDataPairs, opts, escapeHatch);
}
@ -190,10 +193,11 @@ export class Nav implements PublicNav, NavOutlet {
}
@Method()
setRouteId(id: string, _: any = {}): Promise<void> {
setRouteId(id: string, _: any = {}): Promise<any> {
assert(this.useRouter, 'routing is disabled');
const active = this.getActive();
if (active && active.component === id) {
return Promise.resolve();
return Promise.resolve(null);
}
return this.setRoot(id);
}
@ -224,7 +228,7 @@ export class Nav implements PublicNav, NavOutlet {
@Method()
isTransitioning() {
return !!this.transitionId;
return this.transitionId >= 0;
}
@Method()
@ -238,13 +242,8 @@ export class Nav implements PublicNav, NavOutlet {
}
@Method()
getTransitionInfoForUrl(url: string): TransitionInstruction {
return urlMap.get(url);
}
@Method()
clearTransitionInfoForUrl(url: string) {
urlMap.delete(url);
onAllTransitionsComplete() {
return allTransitionsCompleteImpl(this);
}
canSwipeBack(): boolean {
@ -330,48 +329,60 @@ export class Nav implements PublicNav, NavOutlet {
}
export function componentDidLoadImpl(nav: Nav) {
if (nav.initialized) {
return;
}
nav.initialized = true;
nav.navInit.emit();
if (nav.root) {
nav.setRoot(nav.root);
if (!nav.useRouter) {
if (nav.root && !nav.lazy) {
nav.setRoot(nav.root);
}
}
}
export async function pushImpl(nav: Nav, component: any, data: any, opts: NavOptions, escapeHatch: any) {
export async function pushImpl(nav: Nav, component: any, data: any, opts: NavOptions, escapeHatch: EscapeHatch) {
const animation = await hydrateAnimationController(nav.animationCtrl);
return push(nav, nav.delegate, animation, component, data, opts, escapeHatch);
return push(nav, nav.delegate, animation, component, data, opts, escapeHatch).then((navResult) => {
return navResult;
});
}
export async function popImpl(nav: Nav, opts: NavOptions, escapeHatch: any) {
export async function popImpl(nav: Nav, opts: NavOptions, escapeHatch: EscapeHatch) {
const animation = await hydrateAnimationController(nav.animationCtrl);
return pop(nav, nav.delegate, animation, opts, escapeHatch);
return pop(nav, nav.delegate, animation, opts, escapeHatch).then((navResult) => {
return navResult;
});
}
export async function setRootImpl(nav: Nav, component: any, data: any, opts: NavOptions, escapeHatch: any) {
export async function setRootImpl(nav: Nav, component: any, data: any, opts: NavOptions, escapeHatch: EscapeHatch) {
const animation = await hydrateAnimationController(nav.animationCtrl);
return setRoot(nav, nav.delegate, animation, component, data, opts, escapeHatch);
return setRoot(nav, nav.delegate, animation, component, data, opts, escapeHatch).then((navResult) => {
return navResult;
});
}
export async function insertImpl(nav: Nav, insertIndex: number, page: any, params: any, opts: NavOptions, escapeHatch: any) {
export async function insertImpl(nav: Nav, insertIndex: number, page: any, params: any, opts: NavOptions, escapeHatch: EscapeHatch) {
const animation = await hydrateAnimationController(nav.animationCtrl);
return insert(nav, nav.delegate, animation, insertIndex, page, params, opts, escapeHatch);
}
export async function insertPagesImpl(nav: Nav, insertIndex: number, pagesToInsert: any[], opts: NavOptions, escapeHatch: any) {
export async function insertPagesImpl(nav: Nav, insertIndex: number, pagesToInsert: any[], opts: NavOptions, escapeHatch: EscapeHatch) {
const animation = await hydrateAnimationController(nav.animationCtrl);
return insertPages(nav, nav.delegate, animation, insertIndex, pagesToInsert, opts, escapeHatch);
}
export async function popToRootImpl(nav: Nav, opts: NavOptions, escapeHatch: any) {
export async function popToRootImpl(nav: Nav, opts: NavOptions, escapeHatch: EscapeHatch) {
const animation = await hydrateAnimationController(nav.animationCtrl);
return popToRoot(nav, nav.delegate, animation, opts, escapeHatch);
}
export async function popToImpl(nav: Nav, indexOrViewCtrl: any, opts: NavOptions, escapeHatch: any) {
export async function popToImpl(nav: Nav, indexOrViewCtrl: any, opts: NavOptions, escapeHatch: EscapeHatch) {
const animation = await hydrateAnimationController(nav.animationCtrl);
return popTo(nav, nav.delegate, animation, indexOrViewCtrl, opts, escapeHatch);
}
export async function removeImpl(nav: Nav, startIndex: number, removeCount: number, opts: NavOptions, escapeHatch: any) {
export async function removeImpl(nav: Nav, startIndex: number, removeCount: number, opts: NavOptions, escapeHatch: EscapeHatch) {
const animation = await hydrateAnimationController(nav.animationCtrl);
return remove(nav, nav.delegate, animation, startIndex, removeCount, opts, escapeHatch);
}
@ -381,9 +392,9 @@ export async function removeViewImpl(nav: Nav, viewController: PublicViewControl
return removeView(nav, nav.delegate, animation, viewController as ViewController, opts, escapeHatch);
}
export async function setPagesImpl(nav: Nav, componentDataPairs: ComponentDataPair[], opts: NavOptions, escapeHatch: any) {
export async function setPagesImpl(nav: Nav, componentDataPairs: ComponentDataPair[], opts: NavOptions, escapeHatch: EscapeHatch) {
const animation = await hydrateAnimationController(nav.animationCtrl);
return setPages(nav, nav.delegate, animation, componentDataPairs, opts, escapeHatch);
return setPages(nav, nav.delegate, animation, componentDataPairs, opts, escapeHatch, null);
}
export function canGoBackImpl(nav: Nav) {
@ -407,11 +418,10 @@ export function hydrateAnimationController(animationController: AnimationControl
return animationController.create();
}
// public api
export function push(nav: Nav, delegate: FrameworkDelegate, animation: Animation, component: any, data: any, opts: NavOptions, escapeHatch: any): Promise<any> {
return queueTransaction({
export function push(nav: Nav, delegate: FrameworkDelegate, animation: Animation, component: any, data: any, opts: NavOptions, escapeHatch: EscapeHatch): Promise<NavResult> {
return preprocessTransaction({
component: component,
insertStart: -1,
insertViews: [{component, data}],
@ -421,12 +431,12 @@ export function push(nav: Nav, delegate: FrameworkDelegate, animation: Animation
id: nav.navId,
animation,
escapeHatch,
method: 'push'
method: PUSH
});
}
export function insert(nav: Nav, delegate: FrameworkDelegate, animation: Animation, insertIndex: number, component: any, data: any, opts: NavOptions, escapeHatch: any): Promise<any> {
return queueTransaction({
export function insert(nav: Nav, delegate: FrameworkDelegate, animation: Animation, insertIndex: number, component: any, data: any, opts: NavOptions, escapeHatch: EscapeHatch): Promise<NavResult> {
return preprocessTransaction({
component: component,
insertStart: insertIndex,
insertViews: [{ component, data }],
@ -440,8 +450,8 @@ export function insert(nav: Nav, delegate: FrameworkDelegate, animation: Animati
});
}
export function insertPages(nav: Nav, delegate: FrameworkDelegate, animation: Animation, insertIndex: number, insertPages: any[], opts: NavOptions, escapeHatch: any): Promise<any> {
return queueTransaction({
export function insertPages(nav: Nav, delegate: FrameworkDelegate, animation: Animation, insertIndex: number, insertPages: any[], opts: NavOptions, escapeHatch: EscapeHatch): Promise<NavResult> {
return preprocessTransaction({
component: null,
insertStart: insertIndex,
insertViews: insertPages,
@ -455,8 +465,8 @@ export function insertPages(nav: Nav, delegate: FrameworkDelegate, animation: An
});
}
export function pop(nav: Nav, delegate: FrameworkDelegate, animation: Animation, opts: NavOptions, escapeHatch: any): Promise<any> {
return queueTransaction({
export function pop(nav: Nav, delegate: FrameworkDelegate, animation: Animation, opts: NavOptions, escapeHatch: EscapeHatch): Promise<NavResult> {
return preprocessTransaction({
component: null,
removeStart: -1,
removeCount: 1,
@ -466,12 +476,12 @@ export function pop(nav: Nav, delegate: FrameworkDelegate, animation: Animation,
id: nav.navId,
animation,
escapeHatch,
method: 'pop'
method: POP
});
}
export function popToRoot(nav: Nav, delegate: FrameworkDelegate, animation: Animation, opts: NavOptions, escapeHatch: any): Promise<any> {
return queueTransaction({
export function popToRoot(nav: Nav, delegate: FrameworkDelegate, animation: Animation, opts: NavOptions, escapeHatch: EscapeHatch): Promise<NavResult> {
return preprocessTransaction({
component: null,
removeStart: 1,
removeCount: -1,
@ -485,7 +495,7 @@ export function popToRoot(nav: Nav, delegate: FrameworkDelegate, animation: Anim
});
}
export function popTo(nav: Nav, delegate: FrameworkDelegate, animation: Animation, indexOrViewCtrl: any, opts: NavOptions, escapeHatch: any): Promise<any> {
export function popTo(nav: Nav, delegate: FrameworkDelegate, animation: Animation, indexOrViewCtrl: any, opts: NavOptions, escapeHatch: EscapeHatch): Promise<NavResult> {
const config: TransitionInstruction = {
component: null,
removeStart: -1,
@ -504,11 +514,11 @@ export function popTo(nav: Nav, delegate: FrameworkDelegate, animation: Animatio
} else if (isNumber(indexOrViewCtrl)) {
config.removeStart = indexOrViewCtrl + 1;
}
return queueTransaction(config);
return preprocessTransaction(config);
}
export function remove(nav: Nav, delegate: FrameworkDelegate, animation: Animation, startIndex: number, removeCount = 1, opts: NavOptions, escapeHatch: any): Promise<any> {
return queueTransaction({
export function remove(nav: Nav, delegate: FrameworkDelegate, animation: Animation, startIndex: number, removeCount = 1, opts: NavOptions, escapeHatch: EscapeHatch): Promise<NavResult> {
return preprocessTransaction({
component: null,
removeStart: startIndex,
removeCount: removeCount,
@ -522,8 +532,8 @@ export function remove(nav: Nav, delegate: FrameworkDelegate, animation: Animati
});
}
export function removeView(nav: Nav, delegate: FrameworkDelegate, animation: Animation, viewController: ViewController, opts: NavOptions, escapeHatch: any): Promise<any> {
return queueTransaction({
export function removeView(nav: Nav, delegate: FrameworkDelegate, animation: Animation, viewController: ViewController, opts: NavOptions, escapeHatch: EscapeHatch): Promise<NavResult> {
return preprocessTransaction({
component: null,
removeView: viewController,
removeStart: 0,
@ -538,18 +548,18 @@ export function removeView(nav: Nav, delegate: FrameworkDelegate, animation: Ani
});
}
export function setRoot(nav: Nav, delegate: FrameworkDelegate, animation: Animation, component: any, data: any, opts: NavOptions, escapeHatch: any): Promise<any> {
return setPages(nav, delegate, animation, [{ component, data }], opts, escapeHatch);
export function setRoot(nav: Nav, delegate: FrameworkDelegate, animation: Animation, component: any, data: any, opts: NavOptions, escapeHatch: EscapeHatch): Promise<NavResult> {
return setPages(nav, delegate, animation, [{ component, data }], opts, escapeHatch, SET_ROOT);
}
export function setPages(nav: Nav, delegate: FrameworkDelegate, animation: Animation, componentDataPairs: ComponentDataPair[], opts: NavOptions, escapeHatch: any): Promise<any> {
export function setPages(nav: Nav, delegate: FrameworkDelegate, animation: Animation, componentDataPairs: ComponentDataPair[], opts: NavOptions, escapeHatch: EscapeHatch, methodName: string): Promise<NavResult> {
if (!isDef(opts)) {
opts = {};
}
if (opts.animate !== true) {
opts.animate = false;
}
return queueTransaction({
return preprocessTransaction({
component: componentDataPairs.length === 1 ? componentDataPairs[0].component : null,
insertStart: 0,
insertViews: componentDataPairs,
@ -561,22 +571,49 @@ export function setPages(nav: Nav, delegate: FrameworkDelegate, animation: Anima
id: nav.navId,
animation,
escapeHatch,
method: 'setPages'
method: methodName ? methodName : 'setPages'
});
}
// private api, exported for testing
export function queueOrNavigate(ti: TransitionInstruction): void | Promise<NavResult> {
if (isComponentUrl(ti.component)) {
urlMap.set(ti.component as string, ti);
window.location.href = ti.component;
export function preprocessTransaction(ti: TransitionInstruction): Promise<NavResult> {
if (isUrl(ti.component)) {
if (ti.method === PUSH || ti.method === POP || ti.method === SET_ROOT) {
return navigateToUrl(ti.nav, normalizeUrl(ti.component) as string, ti.method);
} else {
return Promise.reject(new Error('only push, pop, and setRoot methods support urls'));
}
}
const response = checkIfPopRedirectRequired(ti);
if (response.required) {
return navigateToUrl(ti.nav, response.url, POP);
}
return queueTransaction(ti);
}
export function isComponentUrl(component: any) {
return component && typeof component === 'string' && component.charAt(0) === '/';
export function isUrl(component: any): boolean {
return typeof component === 'string' && component.charAt(0) === '/';
}
export function navigateToUrl(nav: Nav, url: string, method: string): Promise<NavResult> {
let routingPromise: Promise<any> = null;
return new Promise((resolve, reject) => {
nav.urlExternalNavMap.set(url, {
url,
method,
resolve,
reject
});
if (!nav.routerDelegate) {
nav.routerDelegate = new DomRouterDelegate();
}
routingPromise = nav.routerDelegate.pushUrlState(url);
}).then((navResult: NavResult) => {
return routingPromise.then(() => {
return navResult;
});
});
}
export function queueTransaction(ti: TransitionInstruction): Promise<NavResult> {
@ -617,16 +654,68 @@ export function nextTransaction(nav: Nav): Promise<any> {
const topTransaction = getTopTransaction(nav.navId);
if (!topTransaction) {
// cool, there are no transitions going for this nav
processAllTransitionCompleteQueue(nav.navId);
return Promise.resolve();
}
return doNav(nav, topTransaction);
}
export function checkIfPopRedirectRequired(ti: TransitionInstruction): IsRedirectRequired {
if (ti.method === POP) {
if (ti.escapeHatch.fromExternalRouter) {
// if the pop method is called from a router, that means the redirect already happened
// so just do a normal pop because the url is in a good place. Basically, the router is telling us to
// pop
return {
required: false
};
}
// check if we need to redirect to a url for the pop operation
const popToIndex = ti.nav.views.length - 2;
if (popToIndex >= 0) {
const viewController = ti.nav.views[popToIndex];
return {
required: viewController.fromExternalRouter,
url: viewController.url
};
}
}
return {
required: false,
};
}
export function processAllTransitionCompleteQueue(navId: number) {
const queue = allTransitionsCompleteHandlerQueue.get(navId) || [];
for (const callback of queue) {
callback();
}
allTransitionsCompleteHandlerQueue.set(navId, []);
}
export function allTransitionsCompleteImpl(nav: Nav) {
return new Promise((resolve) => {
const queue = transitionQueue.get(nav.navId) || [];
if (queue.length) {
// there are pending transitions, so queue it up and we'll be notified when it's done
const handlers = allTransitionsCompleteHandlerQueue.get(nav.navId) || [];
handlers.push(resolve);
return allTransitionsCompleteHandlerQueue.set(nav.navId, handlers);
}
// there are no pending transitions, so just resolve right away
return resolve();
});
}
export function doNav(nav: Nav, ti: TransitionInstruction) {
let enteringView: ViewController;
let leavingView: ViewController;
return initializeViewBeforeTransition(nav, ti).then(([_enteringView, _leavingView]) => {
return initializeViewBeforeTransition(ti).then(([_enteringView, _leavingView]) => {
enteringView = _enteringView;
leavingView = _leavingView;
return attachViewToDom(nav, enteringView, ti);
@ -647,8 +736,8 @@ export function successfullyTransitioned(ti: TransitionInstruction) {
return fireError(new Error('Queue is null, the nav must have been destroyed'), ti);
}
ti.nav.isViewInitialized = true;
ti.nav.transitionId = null;
ti.nav.initialized = true;
ti.nav.transitionId = NOT_TRANSITIONING_TRANSITION_ID;
ti.nav.transitioning = false;
// TODO - check if it's a swipe back
@ -739,7 +828,7 @@ export function loadViewAndTransition(nav: Nav, enteringView: ViewController, le
export function executeAsyncTransition(nav: Nav, transition: Transition, enteringView: ViewController, leavingView: ViewController, delegate: FrameworkDelegate, opts: NavOptions, configShouldAnimate: boolean): Promise<void> {
assert(nav.transitioning, 'must be transitioning');
nav.transitionId = null;
nav.transitionId = NOT_TRANSITIONING_TRANSITION_ID;
setZIndex(nav, enteringView, leavingView, opts.direction);
// always ensure the entering view is viewable
@ -751,8 +840,8 @@ export function executeAsyncTransition(nav: Nav, transition: Transition, enterin
// ******** DOM WRITE ****************
leavingView && toggleHidden(leavingView.element, false);
const isFirstPage = !nav.isViewInitialized && nav.views.length === 1;
const shouldNotAnimate = isFirstPage && !nav.isPortal;
const isFirstPage = !nav.initialized && nav.views.length === 1;
const shouldNotAnimate = isFirstPage;
if (configShouldAnimate || shouldNotAnimate) {
opts.animate = false;
}
@ -847,7 +936,7 @@ export function cleanUpView(nav: Nav, delegate: FrameworkDelegate, activeViewCon
// this view comes after the active view
inactiveViewController.willUnload();
promises.push(destroyView(nav, delegate, inactiveViewController));
} else if ( i < activeIndex && !nav.isPortal) {
} else if ( i < activeIndex) {
// this view comes before the active view
// and it is not a portal then ensure it is hidden
toggleHidden(inactiveViewController.element, true);
@ -875,11 +964,11 @@ export function attachViewToDom(nav: Nav, enteringView: ViewController, ti: Tran
return Promise.resolve();
}
export function initializeViewBeforeTransition(nav: Nav, ti: TransitionInstruction): Promise<ViewController[]> {
export function initializeViewBeforeTransition(ti: TransitionInstruction): Promise<ViewController[]> {
let leavingView: ViewController = null;
let enteringView: ViewController = null;
return startTransaction(ti).then(() => {
const viewControllers = convertComponentToViewController(nav, ti);
const viewControllers = convertComponentToViewController(ti);
ti.viewControllers = viewControllers;
leavingView = ti.nav.getActive() as ViewController;
enteringView = getEnteringView(ti, ti.nav, leavingView);
@ -925,7 +1014,7 @@ export function updateNavStacks(enteringView: ViewController, leavingView: ViewC
const finalBalance = ti.nav.views.length + (ti.insertViews ? ti.insertViews.length : 0) - (ti.removeCount ? ti.removeCount : 0);
assert(finalBalance >= 0, 'final balance can not be negative');
if (finalBalance === 0 && !ti.nav.isPortal) {
if (finalBalance === 0) {
console.warn(`You can't remove all the pages in the navigation stack. nav.pop() is probably called too many times.`);
throw new Error('Navigation stack needs at least one root page');
}
@ -1001,7 +1090,7 @@ export function insertViewIntoNav(nav: Nav, view: ViewController, index: number)
assert(view.nav === nav, 'view is not part of the nav');
nav.views.splice(index, 0, nav.views.splice(existingIndex, 1)[0]);
} else {
assert(!view.nav || (nav.isPortal && view.nav === nav), 'nav is used');
assert(!view.nav, 'nav is used');
// this is a new view to add to the stack
// create the new entering view
view.nav = nav;
@ -1117,17 +1206,18 @@ export function getEnteringView(ti: TransitionInstruction, nav: Nav, leavingView
return null;
}
export function convertViewsToViewControllers(pairs: ComponentDataPair[]): ViewController[] {
export function convertViewsToViewControllers(pairs: ComponentDataPair[], escapeHatch: EscapeHatch): ViewController[] {
return pairs.filter(pair => !!pair)
.map(pair => {
return new ViewController(pair.component, pair.data);
const applyEscapeHatch = pair === pairs[pairs.length - 1];
return new ViewController(pair.component, pair.data, applyEscapeHatch ? escapeHatch.fromExternalRouter : false, applyEscapeHatch ? escapeHatch.url : null);
});
}
export function convertComponentToViewController(_: Nav, ti: TransitionInstruction): ViewController[] {
export function convertComponentToViewController(ti: TransitionInstruction): ViewController[] {
if (ti.insertViews) {
assert(ti.insertViews.length > 0, 'length can not be zero');
const viewControllers = convertViewsToViewControllers(ti.insertViews);
const viewControllers = convertViewsToViewControllers(ti.insertViews, ti.escapeHatch);
assert(ti.insertViews.length === viewControllers.length, 'lengths does not match');
if (viewControllers.length === 0) {
throw new Error('No views to insert');
@ -1147,17 +1237,17 @@ export function convertComponentToViewController(_: Nav, ti: TransitionInstructi
}
export function addToQueue(ti: TransitionInstruction) {
const list = queueMap.get(ti.id) || [];
const list = transitionQueue.get(ti.id) || [];
list.push(ti);
queueMap.set(ti.id, list);
transitionQueue.set(ti.id, list);
}
export function getQueue(id: number) {
return queueMap.get(id) || [];
return transitionQueue.get(id) || [];
}
export function resetQueue(id: number) {
queueMap.set(id, []);
transitionQueue.set(id, []);
}
export function getTopTransaction(id: number) {
@ -1167,7 +1257,7 @@ export function getTopTransaction(id: number) {
}
const tmp = queue.concat();
const toReturn = tmp.shift();
queueMap.set(id, tmp);
transitionQueue.set(id, tmp);
return toReturn;
}
@ -1177,6 +1267,7 @@ export function getDefaultTransition(config: Config) {
let viewIds = VIEW_ID_START;
const DISABLE_APP_MINIMUM_DURATION = 64;
const NOT_TRANSITIONING_TRANSITION_ID = -1;
export interface NavEvent extends CustomEvent {
target: HTMLIonNavElement;
@ -1186,3 +1277,18 @@ export interface NavEvent extends CustomEvent {
export interface NavEventDetail {
isPop?: boolean;
}
export function getDefaultEscapeHatch(): EscapeHatch {
return {
fromExternalRouter: false,
};
}
export interface IsRedirectRequired {
required: boolean;
url?: string;
}
export const POP = 'pop';
export const PUSH = 'push';
export const SET_ROOT = 'setRoot';

View File

@ -27,6 +27,11 @@ string
any
#### routerDelegate
#### swipeBackEnabled
boolean
@ -59,6 +64,11 @@ string
any
#### router-delegate
#### swipe-back-enabled
boolean
@ -82,9 +92,6 @@ boolean
#### canGoBack()
#### clearTransitionInfoForUrl()
#### first()
@ -106,9 +113,6 @@ boolean
#### getRouteId()
#### getTransitionInfoForUrl()
#### getViews()
@ -124,6 +128,9 @@ boolean
#### last()
#### onAllTransitionsComplete()
#### pop()

View File

@ -14,10 +14,10 @@ const SHOW_BACK_BTN_CSS = 'show-back-button';
export function buildIOSTransition(rootTransition: Transition, enteringView: ViewController, leavingView: ViewController, opts: AnimationOptions): Promise<Transition> {
const componentReadyPromise: Promise<any>[] = [];
// Let makes sure everything is hydrated and ready to animate
if (enteringView) {
if (enteringView && (enteringView.element as any).componentOnReady) {
componentReadyPromise.push((enteringView.element as any).componentOnReady());
}
if (leavingView) {
if (leavingView && (leavingView.element as any).componentOnReady) {
componentReadyPromise.push((leavingView.element as any).componentOnReady());
}

View File

@ -10,10 +10,10 @@ const SHOW_BACK_BTN_CSS = 'show-back-button';
export function buildMdTransition(rootTransition: Transition, enteringView: ViewController, leavingView: ViewController, opts: AnimationOptions): Promise<Transition> {
const componentReadyPromise: Promise<any>[] = [];
if (enteringView) {
if (enteringView && (enteringView.element as any).componentOnReady) {
componentReadyPromise.push((enteringView.element as any).componentOnReady());
}
if (leavingView) {
if (leavingView && (leavingView.element as any).componentOnReady) {
componentReadyPromise.push((leavingView.element as any).componentOnReady());
}
@ -32,8 +32,7 @@ export function buildMdTransition(rootTransition: Transition, enteringView: View
// animate the component itself
if (backDirection) {
rootTransition.duration(isDef(opts.duration) ? opts.duration : 200).easing('cubic-bezier(0.47,0,0.745,0.715)');
}
else {
} else {
rootTransition.duration(isDef(opts.duration) ? opts.duration : 280).easing('cubic-bezier(0.36,0.66,0.04,1)');
rootTransition

View File

@ -1,7 +1,10 @@
import { FrameworkDelegate, Nav, PublicViewController } from '../../index';
import { STATE_ATTACHED, STATE_DESTROYED, STATE_INITIALIZED, STATE_NEW } from './nav-utils';
import { assert } from '../../utils/helpers';
import {
assert,
normalizeUrl
} from '../../utils/helpers';
export class ViewController implements PublicViewController {
@ -15,13 +18,15 @@ export class ViewController implements PublicViewController {
zIndex: number;
dismissProxy: any;
timestamp: number;
fromExternalRouter: boolean;
url: string;
onDidDismiss: (data: any, role: string) => void;
onWillDismiss: (data: any, role: string) => void;
constructor(public component: any, data?: any) {
initializeNewViewController(this, data);
constructor(public component: any, data: any, fromExternalRouter: boolean, url: string) {
initializeNewViewController(this, data, fromExternalRouter, url);
}
/**
@ -159,8 +164,10 @@ export function didLoadImpl(viewController: ViewController) {
callLifeCycleFunction(viewController.instance, 'ionViewDidLoad');
}
export function initializeNewViewController(viewController: ViewController, data: any) {
export function initializeNewViewController(viewController: ViewController, data: any, fromExternalRouter: boolean, url: string) {
viewController.timestamp = Date.now();
viewController.state = STATE_NEW;
viewController.data = data || {};
viewController.fromExternalRouter = fromExternalRouter;
viewController.url = url && normalizeUrl(url);
}

View File

@ -1,5 +1,5 @@
export interface NavOutlet {
setRouteId(id: any, data?: any): Promise<void>;
setRouteId(id: any, data?: any): Promise<any>;
getRouteId(): string;
getContentElement(): HTMLElement | null;
}

View File

@ -1,5 +1,4 @@
import { Component, Element, Event, EventEmitter, Method, Prop, State, Watch } from '@stencil/core';
import { getNavAsChildIfExists } from '../../utils/helpers';
import { FrameworkDelegate } from '../..';
@ -9,7 +8,7 @@ import { FrameworkDelegate } from '../..';
})
export class Tab {
private loaded = false;
// private loaded = false;
@Element() el: HTMLElement;
@State() init = false;
@ -66,6 +65,7 @@ export class Tab {
@Prop({ mutable: true }) selected = false;
@Watch('selected')
selectedChanged(selected: boolean) {
if (selected) {
@ -81,7 +81,30 @@ export class Tab {
@Method()
setActive(active: boolean): Promise<any> {
this.active = active;
if (this.loaded || !active) {
if (active) {
const nav = getNavAsChildIfExists(this.el);
if (nav) {
// the tab's nav has been initialized externally
if ((window as any).externalNavPromise) {
return (window as any).externalNavPromise.then(() => {
(window as any).externalNavPromise = null;
});
} else {
// the tab's nav has not been initialized externally, so
// check if we need to initiailize it
return (nav as any).componentOnReady().then(() => {
return nav.onAllTransitionsComplete();
}).then(() => {
if (!nav.getViews().length && !nav.isTransitioning() && !nav.initialized) {
return nav.setRoot(nav.root);
}
return Promise.resolve();
});
}
}
}
/*if (this.loaded || !active) {
return Promise.resolve();
}
@ -97,6 +120,8 @@ export class Tab {
promise = Promise.resolve();
}
return promise.then(() => this.fireChildren());
*/
return Promise.resolve();
}
@Method()
@ -110,7 +135,7 @@ export class Tab {
return null;
}
private fireChildren() {
/*private fireChildren() {
const nav = getNavAsChildIfExists(this.el);
if (nav && nav.getViews().length === 0 && nav.root) {
// we need to initialize
@ -119,6 +144,7 @@ export class Tab {
// it's already been initialized if it exists
return Promise.resolve();
}
*/
hostData() {
const visible = this.active && this.selected;
@ -127,7 +153,7 @@ export class Tab {
'aria-labelledby': this.btnId,
'role': 'tabpanel',
class: {
'show-tab': this.active
'show-tab': visible
}
};
}
@ -137,14 +163,15 @@ export class Tab {
}
}
function attachViewToDom(container: HTMLElement, cmp: string): Promise<any> {
/*function attachViewToDom(container: HTMLElement, cmp: string): Promise<any> {
const el = document.createElement(cmp) as HTMLStencilElement;
container.appendChild(el);
if (el.componentOnReady ) {
if (el.componentOnReady) {
return el.componentOnReady();
}
return Promise.resolve();
}
*/
export interface TabEvent extends CustomEvent {
detail: TabEventDetail;

View File

@ -9,6 +9,7 @@ import { Config, NavEventDetail, NavOutlet } from '../../index';
export class Tabs implements NavOutlet {
private ids = -1;
private tabsId: number = (++tabIds);
initialized = false;
@Element() el: HTMLElement;
@ -70,8 +71,14 @@ export class Tabs implements NavOutlet {
this.loadConfig('tabsLayout', 'icon-top');
this.loadConfig('tabsHighlight', true);
this.initTabs();
this.initSelect();
return this.initTabs().then(() => {
if (! (window as any).externalNav) {
return this.initSelect();
}
return null;
}).then(() => {
this.initialized = true;
});
}
componentDidUnload() {
@ -171,16 +178,19 @@ export class Tabs implements NavOutlet {
private initTabs() {
const tabs = this.tabs = Array.from(this.el.querySelectorAll('ion-tab'));
const tabPromises: Promise<any>[] = [];
for (const tab of tabs) {
const id = `t-${this.tabsId}-${++this.ids}`;
tab.btnId = 'tab-' + id;
tab.id = 'tabpanel-' + id;
tabPromises.push((tab as any).componentOnReady());
}
return Promise.all(tabPromises);
}
private initSelect() {
if (document.querySelector('ion-router')) {
return;
return Promise.resolve();
}
// find pre-selected tabs
const selectedTab = this.tabs.find(t => t.selected) ||
@ -192,10 +202,10 @@ export class Tabs implements NavOutlet {
tab.selected = false;
}
}
if (selectedTab) {
selectedTab.setActive(true);
}
this.selectedTab = selectedTab;
const promise = selectedTab ? selectedTab.setActive(true) : Promise.resolve();
return promise.then(() => {
this.selectedTab = selectedTab;
});
}
private loadConfig(attrKey: string, fallback: any) {

View File

@ -146,6 +146,11 @@ export interface FrameworkDelegate {
removeViewFromDom(elementOrContainerToUnmountFrom: any, elementOrComponentToUnmount: any, escapeHatch?: any): Promise<void>;
}
export interface RouterDelegate {
pushUrlState(urlSegment: string, stateObject?: any, title?: string): Promise<any>;
popUrlState(): Promise<any>;
}
export interface FrameworkMountingData {
element: HTMLElement;
component: any;

View File

@ -28,12 +28,4 @@ export class DomFrameworkDelegate implements FrameworkDelegate {
parentElement.removeChild(childElement);
return Promise.resolve();
}
shouldDeferToRouter(_elementOrComponentToMount: any): Promise<boolean> {
return Promise.resolve(false);
}
routeToUrl(_elementOrComponentToMount: any): Promise<any> {
return Promise.resolve('todo');
}
}

View File

@ -0,0 +1,16 @@
import { RouterDelegate } from '../index';
export class DomRouterDelegate implements RouterDelegate {
pushUrlState(urlSegment: string, stateObject: any = null, title = ''): Promise<any> {
history.pushState(stateObject, title, urlSegment);
return Promise.resolve();
}
popUrlState() {
history.back();
return Promise.resolve();
}
}

View File

@ -303,3 +303,16 @@ export function getNavAsChildIfExists(element: HTMLElement): HTMLIonNavElement|n
}
return null;
}
export function normalizeUrl(url: string) {
url = url.trim();
if (url.charAt(0) !== '/') {
// ensure first char is a /
url = '/' + url;
}
if (url.length > 1 && url.charAt(url.length - 1) === '/') {
// ensure last char is not a /
url = url.substr(0, url.length - 1);
}
return url;
}

View File

@ -1,8 +1,8 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"allowUnreachableCode": false,
"alwaysStrict": true,
"declaration": true,
"experimentalDecorators": true,
"forceConsistentCasingInFileNames": true,
"jsx": "react",

View File

@ -14,12 +14,15 @@ const routes: Routes = [
{ path: 'content', loadChildren: 'app/content/content.module#ContentModule' },
{ path: 'toast', loadChildren: 'app/toast/toast.module#ToastModule' },
{ path: 'loading', loadChildren: 'app/loading/loading.module#LoadingModule' },
{ path: 'no-routing-nav', loadChildren: 'app/no-routing-nav/no-routing-nav.module#NoRoutingNavModule' },
{ path: 'modal', loadChildren: 'app/modal/modal.module#ModalModule' },
{ path: 'popover', loadChildren: 'app/popover/popover.module#PopoverModule' },
{ path: 'segment', loadChildren: 'app/segment/segment.module#SegmentModule' },
{ path: 'virtual-scroll', loadChildren: 'app/virtual-scroll/virtual-scroll.module#VirtualScrollModule' },
{ path: 'no-routing-nav', loadChildren: 'app/no-routing-nav/no-routing-nav.module#NoRoutingNavModule' },
{ path: 'simple-nav', loadChildren: 'app/simple-nav/simple-nav.module#SimpleNavModule' },
{ path: 'nested-nav', loadChildren: 'app/nested-nav/nested-nav.module#NestedNavModule' },
{ path: 'nav-then-tabs', loadChildren: 'app/nav-then-tabs/nav-then-tabs.module#NavThenTabsModule' },
];
@NgModule({

View File

@ -51,5 +51,19 @@
<div>
<h2>Nav Tests</h2>
<a href='no-routing-nav'>No Routing</a>
<ul>
<li>
<a href='no-routing-nav'>No Routing</a>
</li>
<li>
<a href='simple-nav/page-one'>Simple Nav</a>
</li>
<li>
<a href='nested-nav/nested-page-one'>Nested Nav</a>
</li>
<li>
<a href='nav-then-tabs/login'>Nav Then Tabs</a>
</li>
</ul>
</div>

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginPage } from './login';
const routes: Routes = [
{ path: '', component: LoginPage}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class LoginRoutingModule { }

View File

@ -0,0 +1,17 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LoginPage } from './login';
import { LoginRoutingModule } from './login-routing.module';
@NgModule({
imports: [
CommonModule,
LoginRoutingModule
],
declarations: [LoginPage],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class LoginModule { }

View File

@ -0,0 +1,38 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { NavController } from '@ionic/angular';
@Component({
selector: 'login',
template: `
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Login Page</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Login - {{ts}}
<div>
<ion-button (click)="pushPageTwoComponent()">Login to app</ion-button>
</div>
</ion-content>
</ion-page>
`
})
export class LoginPage {
ts: number;
constructor(private navController: NavController) {
setInterval(() => {
this.ts = Date.now();
}, 500);
}
pushPageTwoComponent() {
this.navController.push('/nav-then-tabs/app/tabs/(tab-one:one)');
}
}

View File

@ -0,0 +1,21 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { NavThenTabsPageComponent } from './nav-then-tabs.component';
const routes: Routes = [
{
path: '',
component: NavThenTabsPageComponent,
children: [
{ path: 'login', loadChildren: './login/login.module#LoginModule' },
{ path: 'app', loadChildren: './tabs-page/tabs-page.module#TabsPageModule' }
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class NavThenTabsRoutingModule { }

View File

@ -0,0 +1,15 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-nav-page',
template: `
<ion-app>
<ion-nav></ion-nav>
</ion-app>
`
})
export class NavThenTabsPageComponent {
constructor() {
}
}

View File

@ -0,0 +1,21 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { NavThenTabsPageComponent } from './nav-then-tabs.component';
import { NavThenTabsRoutingModule } from './nav-then-tabs-routing.module';
import { IonicAngularModule, IonicRouterModule } from '@ionic/angular';
@NgModule({
declarations: [
NavThenTabsPageComponent,
],
imports: [
CommonModule,
IonicAngularModule,
IonicRouterModule,
NavThenTabsRoutingModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class NavThenTabsModule {}

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { TabOnePageOne } from './tab-one-page-one';
const routes: Routes = [
{ path: '', component: TabOnePageOne}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class TabOnePageOneRoutingModule { }

View File

@ -0,0 +1,17 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TabOnePageOne } from './tab-one-page-one';
import { TabOnePageOneRoutingModule } from './tab-one-page-one-routing.module';
@NgModule({
imports: [
CommonModule,
TabOnePageOneRoutingModule
],
declarations: [TabOnePageOne],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class TabOnePageOneModule { }

View File

@ -0,0 +1,34 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { NavController } from '@ionic/angular';
@Component({
selector: 'tab-one-page-one',
template: `
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Tab One Page One</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Tab One Page One {{ts}}
<div>
<ion-button (click)="next()">Go to Page Two</ion-button>
</div>
</ion-content>
</ion-page>
`
})
export class TabOnePageOne {
ts: number;
constructor(private navController: NavController) {
setInterval(() => {
this.ts = Date.now();
}, 500);
}
next() {
this.navController.push('/nav-then-tabs/app/tabs/(tab-one:two)');
}
}

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { TabOnePageThree } from './tab-one-page-three';
const routes: Routes = [
{ path: '', component: TabOnePageThree}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class TabOnePageThreeRoutingModule { }

View File

@ -0,0 +1,17 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TabOnePageThree } from './tab-one-page-three';
import { TabOnePageThreeRoutingModule } from './tab-one-page-three-routing.module';
@NgModule({
imports: [
CommonModule,
TabOnePageThreeRoutingModule
],
declarations: [TabOnePageThree],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class TabOnePageThreeModule { }

View File

@ -0,0 +1,34 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { NavController } from '@ionic/angular';
@Component({
selector: 'tab-one-page-three',
template: `
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Tab One Page Three</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Tab One Page Three {{ts}}
<div>
<ion-button (click)="back()">Go Back</ion-button>
</div>
</ion-content>
</ion-page>
`
})
export class TabOnePageThree {
ts: number;
constructor(private navController: NavController) {
setInterval(() => {
this.ts = Date.now();
}, 500);
}
back() {
this.navController.pop();
}
}

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { TabOnePageTwo } from './tab-one-page-two';
const routes: Routes = [
{ path: '', component: TabOnePageTwo}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class TabOnePageTwoRoutingModule { }

View File

@ -0,0 +1,17 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TabOnePageTwo } from './tab-one-page-two';
import { TabOnePageTwoRoutingModule } from './tab-one-page-two-routing.module';
@NgModule({
imports: [
CommonModule,
TabOnePageTwoRoutingModule
],
declarations: [TabOnePageTwo],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class TabOnePageTwoModule { }

View File

@ -0,0 +1,41 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { NavController } from '@ionic/angular';
@Component({
selector: 'tab-one-page-two',
template: `
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Tab One Page Two</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Tab One Page Two {{ts}}
<div>
<ion-button (click)="next()">Go to Page Three</ion-button>
</div>
<div>
<ion-button (click)="back()">Go Back</ion-button>
</div>
</ion-content>
</ion-page>
`
})
export class TabOnePageTwo {
ts: number;
constructor(private navController: NavController) {
setInterval(() => {
this.ts = Date.now();
}, 500);
}
next() {
this.navController.push('/nav-then-tabs/app/tabs/(tab-one:three)');
}
back() {
this.navController.pop();
}
}

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { TabThreePageOne } from './tab-three-page-one';
const routes: Routes = [
{ path: '', component: TabThreePageOne}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class TabThreePageOneRoutingModule { }

View File

@ -0,0 +1,17 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TabThreePageOne } from './tab-three-page-one';
import { TabThreePageOneRoutingModule } from './tab-three-page-one-routing.module';
@NgModule({
imports: [
CommonModule,
TabThreePageOneRoutingModule
],
declarations: [TabThreePageOne],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class TabThreePageOneModule { }

View File

@ -0,0 +1,34 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { NavController } from '@ionic/angular';
@Component({
selector: 'tab-three-page-one',
template: `
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Tab Three Page One</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Tab Three Page One {{ts}}
<div>
<ion-button (click)="next()">Go to Page Two</ion-button>
</div>
</ion-content>
</ion-page>
`
})
export class TabThreePageOne {
ts: number;
constructor(private navController: NavController) {
setInterval(() => {
this.ts = Date.now();
}, 500);
}
next() {
this.navController.push('/nav-then-tabs/app/tabs/(tab-three:two)');
}
}

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { TabThreePageThree } from './tab-three-page-three';
const routes: Routes = [
{ path: '', component: TabThreePageThree}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class TabThreePageThreeRoutingModule { }

View File

@ -0,0 +1,17 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TabThreePageThree } from './tab-three-page-three';
import { TabThreePageThreeRoutingModule } from './tab-three-page-three-routing.module';
@NgModule({
imports: [
CommonModule,
TabThreePageThreeRoutingModule
],
declarations: [TabThreePageThree],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class TabThreePageThreeModule { }

View File

@ -0,0 +1,34 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { NavController } from '@ionic/angular';
@Component({
selector: 'tab-three-page-three',
template: `
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Tab Three Page Three</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Tab Three Page Three {{ts}}
<div>
<ion-button (click)="back()">Go Back</ion-button>
</div>
</ion-content>
</ion-page>
`
})
export class TabThreePageThree {
ts: number;
constructor(private navController: NavController) {
setInterval(() => {
this.ts = Date.now();
}, 500);
}
back() {
this.navController.pop();
}
}

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { TabThreePageTwo } from './tab-three-page-two';
const routes: Routes = [
{ path: '', component: TabThreePageTwo}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class TabThreePageTwoRoutingModule { }

View File

@ -0,0 +1,17 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TabThreePageTwo } from './tab-three-page-two';
import { TabThreePageTwoRoutingModule } from './tab-three-page-two-routing.module';
@NgModule({
imports: [
CommonModule,
TabThreePageTwoRoutingModule
],
declarations: [TabThreePageTwo],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class TabThreePageTwoModule { }

View File

@ -0,0 +1,41 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { NavController } from '@ionic/angular';
@Component({
selector: 'tab-three-page-two',
template: `
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Tab Three Page Two</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Tab Three Page Two {{ts}}
<div>
<ion-button (click)="next()">Go to Page Three</ion-button>
</div>
<div>
<ion-button (click)="back()">Go Back</ion-button>
</div>
</ion-content>
</ion-page>
`
})
export class TabThreePageTwo {
ts: number;
constructor(private navController: NavController) {
setInterval(() => {
this.ts = Date.now();
}, 500);
}
next() {
this.navController.push('/nav-then-tabs/app/tabs/(tab-three:three)');
}
back() {
this.navController.pop();
}
}

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { TabTwoPageOne } from './tab-two-page-one';
const routes: Routes = [
{ path: '', component: TabTwoPageOne}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class TabTwoPageOneRoutingModule { }

View File

@ -0,0 +1,17 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TabTwoPageOne } from './tab-two-page-one';
import { TabTwoPageOneRoutingModule } from './tab-two-page-one-routing.module';
@NgModule({
imports: [
CommonModule,
TabTwoPageOneRoutingModule
],
declarations: [TabTwoPageOne],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class TabTwoPageOneModule { }

View File

@ -0,0 +1,34 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { NavController } from '@ionic/angular';
@Component({
selector: 'tab-two-page-one',
template: `
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Tab Two Page One</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Tab Two Page One {{ts}}
<div>
<ion-button (click)="next()">Go to Page Two</ion-button>
</div>
</ion-content>
</ion-page>
`
})
export class TabTwoPageOne {
ts: number;
constructor(private navController: NavController) {
setInterval(() => {
this.ts = Date.now();
}, 500);
}
next() {
this.navController.push('/nav-then-tabs/app/tabs/(tab-two:two)');
}
}

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { TabTwoPageThree } from './tab-two-page-three';
const routes: Routes = [
{ path: '', component: TabTwoPageThree}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class TabTwoPageThreeRoutingModule { }

View File

@ -0,0 +1,17 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TabTwoPageThree } from './tab-two-page-three';
import { TabTwoPageThreeRoutingModule } from './tab-two-page-three-routing.module';
@NgModule({
imports: [
CommonModule,
TabTwoPageThreeRoutingModule
],
declarations: [TabTwoPageThree],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class TabTwoPageThreeModule { }

View File

@ -0,0 +1,34 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { NavController } from '@ionic/angular';
@Component({
selector: 'tab-two-page-three',
template: `
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Tab Two Page Three</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Tab Two Page Three {{ts}}
<div>
<ion-button (click)="back()">Go Back</ion-button>
</div>
</ion-content>
</ion-page>
`
})
export class TabTwoPageThree {
ts: number;
constructor(private navController: NavController) {
setInterval(() => {
this.ts = Date.now();
}, 500);
}
back() {
this.navController.pop();
}
}

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { TabTwoPageTwo } from './tab-two-page-two';
const routes: Routes = [
{ path: '', component: TabTwoPageTwo}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class TabTwoPageTwoRoutingModule { }

View File

@ -0,0 +1,17 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TabTwoPageTwo } from './tab-two-page-two';
import { TabTwoPageTwoRoutingModule } from './tab-two-page-two-routing.module';
@NgModule({
imports: [
CommonModule,
TabTwoPageTwoRoutingModule
],
declarations: [TabTwoPageTwo],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class TabTwoPageTwoModule { }

View File

@ -0,0 +1,41 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { NavController } from '@ionic/angular';
@Component({
selector: 'tab-two-page-two',
template: `
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Tab Two Page Two</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Tab Two Page Two {{ts}}
<div>
<ion-button (click)="next()">Go to Page Three</ion-button>
</div>
<div>
<ion-button (click)="back()">Go Back</ion-button>
</div>
</ion-content>
</ion-page>
`
})
export class TabTwoPageTwo {
ts: number;
constructor(private navController: NavController) {
setInterval(() => {
this.ts = Date.now();
}, 500);
}
next() {
this.navController.push('/nav-then-tabs/app/tabs/(tab-two:three)');
}
back() {
this.navController.pop();
}
}

View File

@ -0,0 +1,76 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { TabsPage } from './tabs-page';
import { TabOnePageOne } from '../tab-one-page-one/tab-one-page-one';
import { TabOnePageTwo } from '../tab-one-page-two/tab-one-page-two';
import { TabOnePageThree } from '../tab-one-page-three/tab-one-page-three';
import { TabTwoPageOne } from '../tab-two-page-one/tab-two-page-one';
import { TabTwoPageTwo } from '../tab-two-page-two/tab-two-page-two';
import { TabTwoPageThree } from '../tab-two-page-three/tab-two-page-three';
import { TabThreePageOne } from '../tab-three-page-one/tab-three-page-one';
import { TabThreePageTwo } from '../tab-three-page-two/tab-three-page-two';
import { TabThreePageThree } from '../tab-three-page-three/tab-three-page-three';
const routes: Routes = [
{
path: 'tabs',
component: TabsPage,
children: [
{
path: 'one',
component: TabOnePageOne,
outlet: 'tab-one'
},
{
path: 'two',
component: TabOnePageTwo,
outlet: 'tab-one'
},
{
path: 'three',
component: TabOnePageThree,
outlet: 'tab-one'
},
{
path: 'one',
component: TabTwoPageOne,
outlet: 'tab-two'
},
{
path: 'two',
component: TabTwoPageTwo,
outlet: 'tab-two'
},
{
path: 'three',
component: TabTwoPageThree,
outlet: 'tab-two'
},
{
path: 'one',
component: TabThreePageOne,
outlet: 'tab-three'
},
{
path: 'two',
component: TabThreePageTwo,
outlet: 'tab-three'
},
{
path: 'three',
component: TabThreePageThree,
outlet: 'tab-three'
}
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class TabsPageRoutingModule { }

View File

@ -0,0 +1,52 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
IonicAngularModule,
IonicRouterModule
} from '@ionic/angular';
import { TabsPage } from './tabs-page';
import { TabsPageRoutingModule } from './tabs-page-routing.module';
import { TabOnePageOneModule } from '../tab-one-page-one/tab-one-page-one.module';
import { TabOnePageTwoModule } from '../tab-one-page-two/tab-one-page-two.module';
import { TabOnePageThreeModule } from '../tab-one-page-three/tab-one-page-three.module';
import { TabTwoPageOneModule } from '../tab-two-page-one/tab-two-page-one.module';
import { TabTwoPageTwoModule } from '../tab-two-page-two/tab-two-page-two.module';
import { TabTwoPageThreeModule } from '../tab-two-page-three/tab-two-page-three.module';
import { TabThreePageOneModule } from '../tab-three-page-one/tab-three-page-one.module';
import { TabThreePageTwoModule } from '../tab-three-page-two/tab-three-page-two.module';
import { TabThreePageThreeModule } from '../tab-three-page-three/tab-three-page-three.module';
@NgModule({
imports: [
CommonModule,
TabsPageRoutingModule,
IonicAngularModule,
IonicRouterModule,
TabOnePageOneModule,
TabOnePageTwoModule,
TabOnePageThreeModule,
TabTwoPageOneModule,
TabTwoPageTwoModule,
TabTwoPageThreeModule,
TabThreePageOneModule,
TabThreePageTwoModule,
TabThreePageThreeModule
],
declarations: [
TabsPage,
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class TabsPageModule { }

View File

@ -0,0 +1,38 @@
import { Component, ElementRef, ViewChild, ViewEncapsulation } from '@angular/core';
import { Location } from '@angular/common';
import { Router } from '@angular/router';
import { NavController } from '@ionic/angular';
@Component({
selector: 'tabs-page',
template: `
<ion-tabs>
<ion-tab title="Tab One" icon="star" #tabOne>
<ion-nav [root]="tabOnePageOne" name="tab-one" lazy="true"></ion-nav>
</ion-tab>
<ion-tab title="Tab Two" icon="globe" #tabTwo>
<ion-nav [root]="tabTwoPageOne" name="tab-two" lazy="true"></ion-nav>
</ion-tab>
<ion-tab title="Tab Three" icon="logo-facebook" #tabThree>
<ion-nav [root]="tabThreePageOne" name="tab-three" lazy="true"></ion-nav>
</ion-tab>
</ion-tabs>
`
})
export class TabsPage {
tabOnePageOne = '/nav-then-tabs/app/tabs/(tab-one:one)';
tabTwoPageOne = '/nav-then-tabs/app/tabs/(tab-two:one)';
tabThreePageOne = '/nav-then-tabs/app/tabs/(tab-three:one)';
@ViewChild('tabOne') tabOne: ElementRef;
@ViewChild('tabTwo') tabTwo: ElementRef;
@ViewChild('tabThree') tabThree: ElementRef;
constructor(private router: Router, private location: Location) {
}
goBack() {
window.history.back();
}
}

View File

@ -0,0 +1,22 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { NestedNavPageComponent } from './nested-nav.component';
const routes: Routes = [
{
path: '',
component: NestedNavPageComponent,
children: [
{ path: 'nested-page-one', loadChildren: './nested-page-one/page-one.module#PageOneModule' },
{ path: 'nested-page-two', loadChildren: './nested-page-two/page-two.module#PageTwoModule' },
{ path: 'nested-page-three', loadChildren: './nested-page-three/page-three.module#PageThreeModule' }
]
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class NestedNavRoutingModule { }

View File

@ -0,0 +1,15 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-nav-page',
template: `
<ion-app>
<ion-nav></ion-nav>
</ion-app>
`
})
export class NestedNavPageComponent {
constructor() {
}
}

View File

@ -0,0 +1,21 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { NestedNavPageComponent } from './nested-nav.component';
import { NestedNavRoutingModule } from './nested-nav-routing.module';
import { IonicAngularModule, IonicRouterModule } from '@ionic/angular';
@NgModule({
declarations: [
NestedNavPageComponent,
],
imports: [
CommonModule,
IonicAngularModule,
IonicRouterModule,
NestedNavRoutingModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class NestedNavModule {}

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { PageOne } from './page-one';
const routes: Routes = [
{ path: '', component: PageOne}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class PageOneRoutingModule { }

View File

@ -0,0 +1,17 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PageOne } from './page-one';
import { PageOneRoutingModule } from './page-one-routing.module';
@NgModule({
imports: [
CommonModule,
PageOneRoutingModule
],
declarations: [PageOne],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class PageOneModule { }

View File

@ -0,0 +1,36 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { Router } from '@angular/router';
import { NavController } from '@ionic/angular';
@Component({
selector: 'page-one',
template: `
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Nested Page One</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Page One - {{ts}}
<div>
<ion-button (click)="pushPageTwoComponent()">Go to Page Two</ion-button>
</div>
</ion-content>
</ion-page>
`
})
export class PageOne {
ts: number;
constructor(private navController: NavController) {
setInterval(() => {
this.ts = Date.now();
}, 500);
}
pushPageTwoComponent() {
this.navController.push('/nested-nav/nested-page-two/section-one');
}
}

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { PageThree } from './page-three';
const routes: Routes = [
{ path: '', component: PageThree }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class PageThreeRoutingModule { }

View File

@ -0,0 +1,19 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PageThree } from './page-three';
import { PageThreeRoutingModule } from './page-three-routing.module';
@NgModule({
imports: [
CommonModule,
PageThreeRoutingModule
],
declarations: [
PageThree
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class PageThreeModule { }

View File

@ -0,0 +1,39 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { Location } from '@angular/common';
import { Router } from '@angular/router';
import { NavController } from '@ionic/angular';
@Component({
selector: 'page-three',
template: `
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Page Three</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Page Three {{ts}}
<div>
<ion-button (click)="navPop()">Go Back</ion-button>
</div>
</ion-content>
</ion-page>
`
})
export class PageThree {
ts: number;
constructor(private router: Router, private location: Location) {
setInterval(() => {
this.ts = Date.now();
}, 500);
}
navPop() {
window.history.back();
}
}

View File

@ -0,0 +1,29 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { PageTwo } from './page-two';
import { PageTwoSectionOne } from './page-two-section-one';
import { PageTwoSectionTwo } from './page-two-section-two';
const routes: Routes = [
{
path: '',
component: PageTwo,
children: [
{
path: 'section-one',
component: PageTwoSectionOne,
},
{
path: 'section-two',
component: PageTwoSectionTwo,
}
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class PageTwoRoutingModule { }

View File

@ -0,0 +1,43 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { Router } from '@angular/router';
import { NavController } from '@ionic/angular';
@Component({
selector: 'page-two-section-one',
template: `
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Page Two Section One</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Page Two Section One - TS {{ts}}
<div>
<ion-button (click)="pushPageTwoComponent()">Go to Section Two</ion-button>
</div>
<div>
<ion-button (click)="goBack()">Go Back</ion-button>
</div>
</ion-content>
</ion-page>
`
})
export class PageTwoSectionOne {
ts: number;
constructor(private router: Router) {
setInterval(() => {
this.ts = Date.now();
}, 500);
}
pushPageTwoComponent() {
this.router.navigateByUrl('/nested-nav/nested-page-two/section-two');
}
goBack() {
this.router.navigateByUrl('/nested-nav/nested-page-one');
}
}

View File

@ -0,0 +1,42 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { Router } from '@angular/router';
import { NavController } from '@ionic/angular';
@Component({
selector: 'page-two-section-two',
template: `
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Page Two Section Two</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Page Two Section Two {{ts}}
<div>
<ion-button (click)="pushPageTwoComponent()">Go to Page Three</ion-button>
</div>
<div>
<ion-button (click)="goBack()">Go Back</ion-button>
</div>
</ion-content>
</ion-page>
`
})
export class PageTwoSectionTwo {
ts: number;
constructor(private router: Router) {
setInterval(() => {
this.ts = Date.now();
}, 500);
}
pushPageTwoComponent() {
this.router.navigateByUrl('/nested-nav/nested-page-three');
}
goBack() {
this.router.navigateByUrl('/nested-nav/nested-page-two/section-one');
}
}

View File

@ -0,0 +1,32 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
IonicAngularModule,
IonicRouterModule
} from '@ionic/angular';
import { PageTwo } from './page-two';
import { PageTwoSectionOne } from './page-two-section-one';
import { PageTwoSectionTwo } from './page-two-section-two';
import { PageTwoRoutingModule } from './page-two-routing.module';
@NgModule({
imports: [
CommonModule,
PageTwoRoutingModule,
IonicAngularModule,
IonicRouterModule
],
declarations: [
PageTwo,
PageTwoSectionOne,
PageTwoSectionTwo
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class PageTwoModule { }

View File

@ -0,0 +1,24 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { NavController } from '@ionic/angular';
@Component({
selector: 'page-two',
template: `
<ion-nav></ion-nav>
<!-- <router-outlet></router-outlet> -->
`
})
export class PageTwo {
constructor(private navController: NavController) {
}
pushPageThree() {
this.navController.push('/nested-nav/nested-page-three');
}
goBack() {
window.history.back();
}
}

View File

@ -6,6 +6,9 @@ import { Component } from '@angular/core';
<ion-page>
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Page Three</ion-title>
</ion-toolbar>
</ion-header>

View File

@ -8,6 +8,9 @@ import { PageThree } from './page-three';
<ion-page>
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Page Two</ion-title>
</ion-toolbar>
</ion-header>

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { PageOne } from './page-one';
const routes: Routes = [
{ path: '', component: PageOne}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class PageOneRoutingModule { }

View File

@ -0,0 +1,17 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PageOne } from './page-one';
import { PageOneRoutingModule } from './page-one-routing.module';
@NgModule({
imports: [
CommonModule,
PageOneRoutingModule
],
declarations: [PageOne],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class PageOneModule { }

View File

@ -0,0 +1,40 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { NavController } from '@ionic/angular';
import { PageTwo } from '../page-two/page-two';
@Component({
selector: 'page-one',
template: `
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Simple Page One</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Page One - {{ts}}
<div>
<ion-button (click)="pushPageTwoComponent()">Go to Page Two</ion-button>
</div>
</ion-content>
</ion-page>
`
})
export class PageOne {
ts: number;
constructor(private navController: NavController) {
setInterval(() => {
this.ts = Date.now();
}, 500);
}
pushPageTwoComponent() {
this.navController.push('/simple-nav/page-two');
}
}

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { PageThree } from './page-three';
const routes: Routes = [
{ path: '', component: PageThree }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class PageThreeRoutingModule { }

View File

@ -0,0 +1,19 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PageThree } from './page-three';
import { PageThreeRoutingModule } from './page-three-routing.module';
@NgModule({
imports: [
CommonModule,
PageThreeRoutingModule
],
declarations: [
PageThree
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class PageThreeModule { }

View File

@ -0,0 +1,37 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { NavController } from '@ionic/angular';
@Component({
selector: 'page-three',
template: `
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Page Three</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Page Three {{ts}}
<div>
<ion-button (click)="navPop()">Go Back</ion-button>
</div>
</ion-content>
</ion-page>
`
})
export class PageThree {
ts: number;
constructor(private navController: NavController) {
setInterval(() => {
this.ts = Date.now();
}, 500);
}
navPop() {
this.navController.pop();
}
}

View File

@ -0,0 +1,17 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { PageTwo } from './page-two';
const routes: Routes = [
{
path: '',
component: PageTwo,
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class PageTwoRoutingModule { }

View File

@ -0,0 +1,27 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
IonicAngularModule,
IonicRouterModule
} from '@ionic/angular';
import { PageTwo } from './page-two';
import { PageTwoRoutingModule } from './page-two-routing.module';
@NgModule({
imports: [
CommonModule,
PageTwoRoutingModule,
IonicAngularModule,
IonicRouterModule
],
declarations: [
PageTwo,
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class PageTwoModule { }

View File

@ -0,0 +1,39 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { Location } from '@angular/common';
import { NavController } from '@ionic/angular';
@Component({
selector: 'page-two',
template: `
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Page Two</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Page Two - {{ts}}
<div>
<ion-button (click)="pushPageThreeComponent()">Go to Page Three</ion-button>
</div>
<div>
<ion-button (click)="goBack()">Go Back</ion-button>
</div>
</ion-content>
</ion-page>
`
})
export class PageTwo {
constructor(private navController: NavController) {
}
pushPageThreeComponent() {
this.navController.push('/simple-nav/page-three');
}
goBack() {
this.navController.pop();
}
}

View File

@ -0,0 +1,22 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { SimpleNavPageComponent } from './simple-nav.component';
const routes: Routes = [
{
path: '',
component: SimpleNavPageComponent,
children: [
{ path: 'page-one', loadChildren: './page-one/page-one.module#PageOneModule' },
{ path: 'page-two', loadChildren: './page-two/page-two.module#PageTwoModule' },
{ path: 'page-three', loadChildren: './page-three/page-three.module#PageThreeModule' }
]
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class SimpleNavRoutingModule { }

View File

@ -0,0 +1,15 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-nav-page',
template: `
<ion-app>
<ion-nav></ion-nav>
</ion-app>
`
})
export class SimpleNavPageComponent {
constructor() {
}
}

View File

@ -0,0 +1,21 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { SimpleNavPageComponent } from './simple-nav.component';
import { SimpleNavRoutingModule } from './simple-nav-routing.module';
import { IonicAngularModule, IonicRouterModule } from '@ionic/angular';
@NgModule({
declarations: [
SimpleNavPageComponent,
],
imports: [
CommonModule,
IonicAngularModule,
IonicRouterModule,
SimpleNavRoutingModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class SimpleNavModule {}