fix(tabs): have tabs behavior match nav when navigating back/forth via the url

This commit is contained in:
Dan Bucholtz
2017-07-03 14:31:03 -05:00
parent fce4422ab1
commit 3f39e14f76
34 changed files with 536 additions and 13 deletions

View File

@@ -19,7 +19,6 @@ import { IonicPage, NavController, NavParams } from '../../../../../../..';
<div>
Name: {{paramTwo}}
</div>
<button ion-button (click)="goToNext()">Next</button>
</ion-content>
`
})

View File

@@ -0,0 +1,15 @@
import { Component } from '@angular/core';
@Component({
template: `
<ion-split-pane>
<ion-nav [root]="rootOne"></ion-nav>
<ion-nav [root]="rootTwo" main #content></ion-nav>
</ion-split-pane>
`
})
export class AppComponent {
rootOne = 'NestedNavOnePageOne';
rootTwo = 'NestedNavTwoPageOne';
}

View File

@@ -0,0 +1,17 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { IonicApp, IonicModule } from '../../../../..';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
IonicModule.forRoot(AppComponent, { swipeBackEnabled: true, preloadModules: true }),
],
bootstrap: [IonicApp]
})
export class AppModule {}

View File

@@ -0,0 +1,5 @@
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);

View File

@@ -0,0 +1,9 @@
<ion-header>
<ion-navbar>
<ion-title>Page One</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<h2>Page One</h2>
<button ion-button (click)="goToPageTwo()">Go to Page Two</button>
</ion-content>

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { IonicPageModule } from '../../../../../../..';
import { FirstPage } from './first-page';
@NgModule({
imports: [
IonicPageModule.forChild(FirstPage)
],
declarations: [
FirstPage
]
})
export class FirstPageModule { }

View File

@@ -0,0 +1,15 @@
import { Component } from '@angular/core';
import { IonicPage, NavController, } from '../../../../../../..';
@IonicPage()
@Component({
templateUrl: 'first-page.html'
})
export class FirstPage {
constructor(public nav: NavController) {
}
goToPageTwo() {
this.nav.push('SecondPage', { userId: '123', name: 'Michael Scott'});
}
}

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { IonicPageModule } from '../../../../../../..';
import { NestedNavOnePageOne } from './nested-nav-one-page-one';
@NgModule({
imports: [
IonicPageModule.forChild(NestedNavOnePageOne)
],
declarations: [
NestedNavOnePageOne
]
})
export class NestedNavOnePageOneModule { }

View File

@@ -0,0 +1,15 @@
import { Component } from '@angular/core';
import { IonicPage, NavController, } from '../../../../../../..';
@IonicPage()
@Component({
template: `
<ion-nav [root]="root"></ion-nav>
`
})
export class NestedNavOnePageOne {
root: string = 'NestedNavOnePageTwo';
constructor(public nav: NavController) {
}
}

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { IonicPageModule } from '../../../../../../..';
import { NestedNavOnePageTwo } from './nested-nav-one-page-two';
@NgModule({
imports: [
IonicPageModule.forChild(NestedNavOnePageTwo)
],
declarations: [
NestedNavOnePageTwo
]
})
export class NestedNavOnePageTwoModule { }

View File

@@ -0,0 +1,15 @@
import { Component } from '@angular/core';
import { IonicPage, NavController, } from '../../../../../../..';
@IonicPage()
@Component({
template: `
<ion-nav [root]="root"></ion-nav>
`
})
export class NestedNavOnePageTwo {
root: string = 'FirstPage';
constructor(public nav: NavController) {
}
}

View File

@@ -0,0 +1,15 @@
<ion-header>
<ion-navbar>
<ion-title>Page Two</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<h2>Page Two</h2>
<div>
User ID: {{userId}}
</div>
<div>
Name {{name}}
</div>
<button ion-button (click)="goToNextPage()">Go to Next</button>
</ion-content>

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { IonicPageModule } from '../../../../../../..';
import { SecondPage } from './second-page';
@NgModule({
imports: [
IonicPageModule.forChild(SecondPage)
],
declarations: [
SecondPage
]
})
export class SecondPageModule { }

View File

@@ -0,0 +1,22 @@
import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from '../../../../../../..';
@IonicPage({
segment: 'pageTwo/user/:userId/name/:name'
})
@Component({
templateUrl: 'second-page.html'
})
export class SecondPage {
userId: string;
name: string;
constructor(public nav: NavController, public params: NavParams) {
this.userId = this.params.data.userId;
this.name = this.params.data.name;
}
goToNextPage() {
this.nav.push('ThirdPage', { paramOne: 'mono', paramTwo: 'stereo'});
}
}

View File

@@ -0,0 +1,14 @@
<ion-header>
<ion-navbar>
<ion-title>Page Three</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
Page Three
<div>
Param One: {{paramOne}}
</div>
<div>
Param Two: {{paramTwo}}
</div>
</ion-content>

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { IonicPageModule } from '../../../../../../..';
import { ThirdPage } from './third-page';
@NgModule({
imports: [
IonicPageModule.forChild(ThirdPage)
],
declarations: [
ThirdPage
]
})
export class ThirdPageModule { }

View File

@@ -0,0 +1,17 @@
import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams} from '../../../../../../..';
@IonicPage({
segment: 'thirdPage/paramOne/:paramOne/paramTwo/:paramTwo'
})
@Component({
templateUrl: 'third-page.html'
})
export class ThirdPage {
paramOne: string;
paramTwo: string;
constructor(public nav: NavController, public params: NavParams) {
this.paramOne = params.data.paramOne;
this.paramTwo = params.data.paramTwo;
}
}

View File

@@ -0,0 +1,15 @@
<ion-header>
<ion-navbar>
<ion-title>Page Five</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<h2>Page Five</h2>
<div>
User ID: {{userId}}
</div>
<div>
Name {{name}}
</div>
<button ion-button (click)="goToNextPage()">Go to Next</button>
</ion-content>

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { IonicPageModule } from '../../../../../../..';
import { FifthPage } from './fifth-page';
@NgModule({
imports: [
IonicPageModule.forChild(FifthPage)
],
declarations: [
FifthPage
]
})
export class FifthPageModule { }

View File

@@ -0,0 +1,22 @@
import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from '../../../../../../..';
@IonicPage({
segment: 'pageFive/user/:userId/name/:name'
})
@Component({
templateUrl: 'fifth-page.html'
})
export class FifthPage {
userId: string;
name: string;
constructor(public nav: NavController, public params: NavParams) {
this.userId = this.params.data.userId;
this.name = this.params.data.name;
}
goToNextPage() {
this.nav.push('SixthPage', { paramOne: 'Tobey', paramTwo: 'Holly'});
}
}

View File

@@ -0,0 +1,9 @@
<ion-header>
<ion-navbar>
<ion-title>Page Four</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<h2>Page Four</h2>
<button ion-button (click)="goToPageTwo()">Next</button>
</ion-content>

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { IonicPageModule } from '../../../../../../..';
import { FourthPage } from './fourth-page';
@NgModule({
imports: [
IonicPageModule.forChild(FourthPage)
],
declarations: [
FourthPage
]
})
export class FourthPageModule { }

View File

@@ -0,0 +1,15 @@
import { Component } from '@angular/core';
import { IonicPage, NavController, } from '../../../../../../..';
@IonicPage()
@Component({
templateUrl: 'fourth-page.html'
})
export class FourthPage {
constructor(public nav: NavController) {
}
goToPageTwo() {
this.nav.push('FifthPage', { userId: '567', name: 'Pamela Beasley'});
}
}

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { IonicPageModule } from '../../../../../../..';
import { NestedNavTwoPageOne } from './nested-nav-two-page-one';
@NgModule({
imports: [
IonicPageModule.forChild(NestedNavTwoPageOne)
],
declarations: [
NestedNavTwoPageOne
]
})
export class NestedNavTwoPageOneModule { }

View File

@@ -0,0 +1,15 @@
import { Component } from '@angular/core';
import { IonicPage, NavController, } from '../../../../../../..';
@IonicPage()
@Component({
template: `
<ion-nav [root]="root"></ion-nav>
`
})
export class NestedNavTwoPageOne {
root: string = 'NestedNavTwoPageTwo';
constructor(public nav: NavController) {
}
}

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { IonicPageModule } from '../../../../../../..';
import { NestedNavTwoPageTwo } from './nested-nav-two-page-two';
@NgModule({
imports: [
IonicPageModule.forChild(NestedNavTwoPageTwo)
],
declarations: [
NestedNavTwoPageTwo
]
})
export class NestedNavTwoPageTwoModule { }

View File

@@ -0,0 +1,15 @@
import { Component } from '@angular/core';
import { IonicPage, NavController, } from '../../../../../../..';
@IonicPage()
@Component({
template: `
<ion-nav [root]="root"></ion-nav>
`
})
export class NestedNavTwoPageTwo {
root: string = 'FourthPage';
constructor(public nav: NavController) {
}
}

View File

@@ -0,0 +1,14 @@
<ion-header>
<ion-navbar>
<ion-title>Page Six</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
Page Six
<div>
Param One: {{paramOne}}
</div>
<div>
Param Two: {{paramTwo}}
</div>
</ion-content>

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { IonicPageModule } from '../../../../../../..';
import { SixthPage } from './sixth-page';
@NgModule({
imports: [
IonicPageModule.forChild(SixthPage)
],
declarations: [
SixthPage
]
})
export class SixthPageModule { }

View File

@@ -0,0 +1,18 @@
import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams} from '../../../../../../..';
@IonicPage({
segment: 'sixthPage/paramOne/:paramOne/paramTwo/:paramTwo'
})
@Component({
templateUrl: 'sixth-page.html'
})
export class SixthPage {
paramOne: string;
paramTwo: string;
constructor(public nav: NavController, public params: NavParams) {
this.paramOne = params.data.paramOne;
this.paramTwo = params.data.paramTwo;
}
}

View File

@@ -4,7 +4,7 @@ import { IonicPage, } from '../../../../../..';
@IonicPage()
@Component({
template: `
<ion-tabs>
<ion-tabs name="app">
<ion-tab tabIcon="heart" [root]="tab1" tabTitle="Taco Burrito Enchilada"></ion-tab>
<ion-tab tabIcon="star" [root]="tab2"></ion-tab>
</ion-tabs>

View File

@@ -307,14 +307,33 @@ export class Tab extends NavControllerBase implements ITab {
load(opts: NavOptions, done?: () => void) {
if (this._lazyRootFromUrl || (!this._loaded && this.root)) {
this.setElementClass('show-tab', true);
if (this._lazyRootFromUrl) {
this.push(this._lazyRootFromUrl, this._lazyRootFromUrlData, opts, done);
this._lazyRootFromUrl = null;
this._lazyRootFromUrlData = null;
} else {
this.push(this.root, this.rootParams, opts, done);
// okay, first thing we need to do if check if the view already exists
const nameToUse = this._lazyRootFromUrl ? this._lazyRootFromUrl : this.root;
const dataToUse = this._lazyRootFromUrlData ? this._lazyRootFromUrlData : this.rootParams;
const numViews = this.length() - 1;
for (let i = numViews; i >= 0; i--) {
const viewController = this.getByIndex(i);
if (viewController && (viewController.id === nameToUse || viewController.component === nameToUse)) {
if (i === numViews) {
// this is the last view in the stack and it's the same
// as the segment so there's no change needed
return done();
} else {
// it's not the exact view as the end
// let's have this nav go back to this exact view
return this.popTo(viewController, {
animate: false,
updateUrl: false,
}, done);
}
}
}
this.push(nameToUse, dataToUse, opts, done);
this._lazyRootFromUrl = null;
this._lazyRootFromUrlData = null;
this._loaded = true;
} else {
@@ -324,7 +343,7 @@ export class Tab extends NavControllerBase implements ITab {
this._dom.read(() => {
this.resize();
});
done();
return done();
}
}

View File

@@ -0,0 +1,91 @@
import { mockTab, mockTabs, mockView, mockViews } from '../../../util/mock-providers';
describe('tab', () => {
describe('load', () => {
it('should measure and refresh the tabs', () => {
// TODO - this test is super leaky but I cant come up with a better way short term
const tabs = mockTabs();
const tab = mockTab(tabs, false);
const spy = jasmine.createSpy('done');
spyOn(tab, 'push');
spyOn(tab, 'popTo');
tab.load({}, spy);
expect(tab.push).not.toHaveBeenCalled();
expect(tab.popTo).not.toHaveBeenCalled();
expect(spy).toHaveBeenCalled();
});
it('should reuse the view if its the top view in the stack', () => {
const tabs = mockTabs();
const tab = mockTab(tabs, false);
const spy = jasmine.createSpy('done');
const mockViewOne = mockView('one');
const mockViewTwo = mockView('two');
mockViews(tab, [mockViewOne, mockViewTwo]);
tab._lazyRootFromUrl = 'someValue';
tab._lazyRootFromUrlData = { };
mockViewTwo.id = tab._lazyRootFromUrl;
spyOn(tab, 'push');
spyOn(tab, 'popTo');
tab.load({}, spy);
expect(tab.push).not.toHaveBeenCalled();
expect(tab.popTo).not.toHaveBeenCalled();
expect(spy).toHaveBeenCalled();
});
it('should pop back to a previous view if ', () => {
const tabs = mockTabs();
const tab = mockTab(tabs, false);
const spy = jasmine.createSpy('done');
const mockViewOne = mockView('one');
const mockViewTwo = mockView('two');
mockViews(tab, [mockViewOne, mockViewTwo]);
tab._lazyRootFromUrl = 'someValue';
tab._lazyRootFromUrlData = { };
mockViewOne.id = tab._lazyRootFromUrl;
spyOn(tab, 'push');
spyOn(tab, 'popTo');
tab.load({}, spy);
expect(tab.push).not.toHaveBeenCalled();
expect(tab.popTo).toHaveBeenCalled();
expect(spy).not.toHaveBeenCalled();
});
it('should push the view if it doesnt exist already', () => {
const tabs = mockTabs();
const tab = mockTab(tabs, false);
const spy = jasmine.createSpy('done');
const mockViewOne = mockView('one');
const mockViewTwo = mockView('two');
mockViews(tab, [mockViewOne, mockViewTwo]);
tab._lazyRootFromUrl = 'someValue';
tab._lazyRootFromUrlData = { };
spyOn(tab, 'push');
spyOn(tab, 'popTo');
tab.load({}, spy);
expect(tab.push).toHaveBeenCalled();
expect(tab.popTo).not.toHaveBeenCalled();
expect(spy).not.toHaveBeenCalled();
});
});
});

View File

@@ -470,7 +470,7 @@ export function mockOverlayPortal(app: App, config: Config, plt: MockPlatform):
);
}
export function mockTab(parentTabs: Tabs): Tab {
export function mockTab(parentTabs: Tabs, overrideLoad: boolean = true): Tab {
let platform = mockPlatform();
let config = mockConfig(null, '/', platform);
let app = (<any>parentTabs)._app || mockApp(config, platform);
@@ -500,9 +500,11 @@ export function mockTab(parentTabs: Tabs): Tab {
null
);
tab.load = (opts: any, cb: Function) => {
cb();
};
if (overrideLoad) {
tab.load = (opts: any, cb: Function) => {
cb();
};
}
return tab;
}