fix(nav): do not allow removing all views from a nav

* fix(nav): controller is initialized

* fix(nav): transitioning state is a boolean not a timer

long async promises in canLeave / canEnter can lead to a false negative of isTransitioning()
It is key for the internal consistency of NavController to always know the correct state

* fix(nav): returning Promise<false> in canLeave / canEnter works as expected

* fix(nav): it is not allowed to pop all the views
This commit is contained in:
Adam Bradley
2016-11-14 09:46:45 -06:00
committed by GitHub
2 changed files with 29 additions and 16 deletions

View File

@ -258,7 +258,7 @@ export class FirstPage {
} }
@Component({template: ''}) @Component({template: '<ion-content></ion-content>'})
export class RedirectPage { export class RedirectPage {
constructor(public navCtrl: NavController) { } constructor(public navCtrl: NavController) { }
ionViewDidEnter() { ionViewDidEnter() {

View File

@ -35,7 +35,7 @@ export class NavControllerBase extends Ion implements NavController {
_sbThreshold: number; _sbThreshold: number;
_sbTrns: Transition; _sbTrns: Transition;
_trnsId: number = null; _trnsId: number = null;
_trnsTm: number = 0; _trnsTm: boolean = false;
_viewport: ViewContainerRef; _viewport: ViewContainerRef;
_views: ViewController[] = []; _views: ViewController[] = [];
@ -182,6 +182,7 @@ export class NavControllerBase extends Ion implements NavController {
ti.resolve = (hasCompleted: boolean, isAsync: boolean, enteringName: string, leavingName: string, direction: string) => { ti.resolve = (hasCompleted: boolean, isAsync: boolean, enteringName: string, leavingName: string, direction: string) => {
// transition has successfully resolved // transition has successfully resolved
this._trnsId = null; this._trnsId = null;
this._init = true;
resolve && resolve(hasCompleted, isAsync, enteringName, leavingName, direction); resolve && resolve(hasCompleted, isAsync, enteringName, leavingName, direction);
// let's see if there's another to kick off // let's see if there's another to kick off
@ -252,14 +253,15 @@ export class NavControllerBase extends Ion implements NavController {
if (!ti) { if (!ti) {
return false; return false;
} }
// set that this nav is actively transitioning
this.setTransitioning(true);
// Get entering and leaving views // Get entering and leaving views
const leavingView = this.getActive(); const leavingView = this.getActive();
const enteringView = this._getEnteringView(ti, leavingView); const enteringView = this._getEnteringView(ti, leavingView);
assert(leavingView || enteringView, 'Both leavingView and enteringView are null'); assert(leavingView || enteringView, 'both leavingView and enteringView are null');
// set that this nav is actively transitioning
this.setTransitioning(true);
// Initialize enteringView // Initialize enteringView
if (enteringView && isBlank(enteringView._state)) { if (enteringView && isBlank(enteringView._state)) {
@ -332,6 +334,16 @@ export class NavControllerBase extends Ion implements NavController {
} }
_postViewInit(enteringView: ViewController, leavingView: ViewController, ti: TransitionInstruction, resolve: TransitionResolveFn) { _postViewInit(enteringView: ViewController, leavingView: ViewController, ti: TransitionInstruction, resolve: TransitionResolveFn) {
assert(leavingView || enteringView, 'Both leavingView and enteringView are null');
if (!enteringView && !this._isPortal) {
console.warn(`You can't remove all the pages in the navigation stack. nav.pop() is probably called too many times.`,
this, this.getNativeElement());
ti.reject && ti.reject('navigation stack needs at least one root page');
return false;
}
const opts = ti.opts || {}; const opts = ti.opts || {};
const insertViews = ti.insertViews; const insertViews = ti.insertViews;
const removeStart = ti.removeStart; const removeStart = ti.removeStart;
@ -494,8 +506,13 @@ export class NavControllerBase extends Ion implements NavController {
if (promises.length) { if (promises.length) {
// darn, async promises, gotta wait for them to resolve // darn, async promises, gotta wait for them to resolve
Promise.all(promises) Promise.all(promises)
.then(() => this._postViewInit(enteringView, leavingView, ti, resolve)) .then((values: any[]) => {
.catch(reject); if (values.some(result => result === false)) {
reject(`ionViewCanEnter rejected`);
} else {
this._postViewInit(enteringView, leavingView, ti, resolve);
}
}).catch(reject);
} else { } else {
// synchronous and all tests passed! let's move on already // synchronous and all tests passed! let's move on already
this._postViewInit(enteringView, leavingView, ti, resolve); this._postViewInit(enteringView, leavingView, ti, resolve);
@ -606,7 +623,7 @@ export class NavControllerBase extends Ion implements NavController {
const duration = transition.getDuration(); const duration = transition.getDuration();
// set that this nav is actively transitioning // set that this nav is actively transitioning
this.setTransitioning(true, duration); this.setTransitioning(true);
if (transition.isRoot()) { if (transition.isRoot()) {
// this is the top most, or only active transition, so disable the app // this is the top most, or only active transition, so disable the app
@ -892,7 +909,7 @@ export class NavControllerBase extends Ion implements NavController {
if (this._sbTrns && this._sbGesture) { if (this._sbTrns && this._sbGesture) {
// continue to disable the app while actively dragging // continue to disable the app while actively dragging
this._app.setEnabled(false, ACTIVE_TRANSITION_DEFAULT); this._app.setEnabled(false, ACTIVE_TRANSITION_DEFAULT);
this.setTransitioning(true, ACTIVE_TRANSITION_DEFAULT); this.setTransitioning(true);
// set the transition animation's progress // set the transition animation's progress
this._sbTrns.progressStep(stepValue); this._sbTrns.progressStep(stepValue);
@ -942,15 +959,11 @@ export class NavControllerBase extends Ion implements NavController {
} }
isTransitioning(): boolean { isTransitioning(): boolean {
if (this._trnsTm === 0) { return this._trnsTm;
return false;
}
// using a timestamp instead of boolean incase something goes wrong
return (this._trnsTm > Date.now());
} }
setTransitioning(isTransitioning: boolean, durationPadding: number = ACTIVE_TRANSITION_DEFAULT) { setTransitioning(isTransitioning: boolean) {
this._trnsTm = (isTransitioning ? (Date.now() + durationPadding + ACTIVE_TRANSITION_OFFSET) : 0); this._trnsTm = isTransitioning;
} }
getActive(): ViewController { getActive(): ViewController {