Skip to content
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Location } from '@angular/common';
import { ComponentRef, NgZone } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import type { AnimationBuilder, RouterDirection } from '@ionic/core/components';
import type { AnimationBuilder, NavDirection, RouterDirection } from '@ionic/core/components';

import { bindLifecycleEvents } from '../../providers/angular-delegate';
import { NavController } from '../../providers/nav-controller';
Expand Down Expand Up @@ -62,50 +62,72 @@ export class StackController {
}

setActive(enteringView: RouteView): Promise<StackDidChangeEvent> {
const consumeResult = this.navCtrl.consumeTransition();
const { isDirectionBasedOnNavigationIds, ...consumeResult } = this.navCtrl.consumeTransition();
let { direction, animation, animationBuilder } = consumeResult;
const leavingView = this.activeView;
const tabSwitch = isTabSwitch(enteringView, leavingView);
if (tabSwitch) {
direction = 'back';
animation = undefined;
}

const viewsSnapshot = this.views.slice();

let currentNavigation;
const currentNavigation = this.router.getCurrentNavigation();

const router = this.router as any;
/**
* If the navigation action sets `replaceUrl: true` then we need to make sure
* we remove the last item from our views stack
*/
if (currentNavigation?.extras?.replaceUrl && currentNavigation?.trigger !== 'popstate') {
if (this.views.length > 0) {
this.views.splice(-1, 1);
}
}

// Angular >= 7.2.0
if (router.getCurrentNavigation) {
currentNavigation = router.getCurrentNavigation();
// determine direction based on the order of the views in the stack
const leavingView = this.activeView;
const isEnteringViewReused = this.views.includes(enteringView);
const leavingViewIndex = leavingView ? this.views.indexOf(leavingView) : -1;
const enteringViewIndex = isEnteringViewReused ? this.views.indexOf(enteringView) : this.views.length;
const suggestedDirectionBasedOnStackOrder: NavDirection | undefined =
leavingViewIndex === -1 ? undefined : enteringViewIndex < leavingViewIndex ? 'back' : 'forward';

// Angular < 7.2.0
} else if (router.navigations?.value) {
currentNavigation = router.navigations.value;
}
/**
* The user triggered a back navigation on a page that was navigated to with root. In this case, the new page
* becomes the root and the leavingView is removed.
*
* This can happen e.g. when navigating to a page with navigateRoot and then using the browser back button
*/
const isPopStateTransitionFromRootPage =
direction === 'back' && leavingView?.root && currentNavigation?.trigger === 'popstate';

/**
* If the navigation action
* sets `replaceUrl: true`
* then we need to make sure
* we remove the last item
* from our views stack
* whether direction based on stack order takes precedence over direction based on navigation ids
*
* only applied if the user did not explicitly set the direction
* (e.g. via the NavController, routerLink directive etc.)
*/
if (currentNavigation?.extras?.replaceUrl) {
if (this.views.length > 0) {
this.views.splice(-1, 1);
const useDirectionBasedOnStackOrder = isDirectionBasedOnNavigationIds && suggestedDirectionBasedOnStackOrder;

if (isPopStateTransitionFromRootPage) {
direction = 'root';
animation = undefined;

if (leavingViewIndex >= 0) {
this.views.splice(leavingViewIndex, 1);
}
} else if (useDirectionBasedOnStackOrder) {
direction = suggestedDirectionBasedOnStackOrder;
animation = suggestedDirectionBasedOnStackOrder;
}

const tabSwitch = isTabSwitch(enteringView, leavingView);
if (tabSwitch) {
direction = 'back';
animation = undefined;
}

const reused = this.views.includes(enteringView);
const views = this.insertView(enteringView, direction);

// Trigger change detection before transition starts
// This will call ngOnInit() the first time too, just after the view
// was attached to the dom, but BEFORE the transition starts
if (!reused) {
if (!isEnteringViewReused) {
enteringView.ref.changeDetectorRef.detectChanges();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const insertView = (views: RouteView[], view: RouteView, direction: Route

const setRoot = (views: RouteView[], view: RouteView) => {
views = views.filter((v) => v.stackId !== view.stackId);
view.root = true;
views.push(view);
return views;
};
Expand Down Expand Up @@ -110,4 +111,5 @@ export interface RouteView {
savedExtras?: NavigationExtras;
unlistenEvents: () => void;
animationBuilder?: AnimationBuilder;
root?: boolean;
}
8 changes: 6 additions & 2 deletions packages/angular/common/src/providers/nav-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ export class NavController {
if (router) {
router.events.subscribe((ev) => {
if (ev instanceof NavigationStart) {
// restoredState is set if the browser back/forward button is used
const id = ev.restoredState ? ev.restoredState.navigationId : ev.id;
this.guessDirection = id < this.lastNavId ? 'back' : 'forward';
this.guessAnimation = !ev.restoredState ? this.guessDirection : undefined;
this.guessAnimation = this.guessDirection = id < this.lastNavId ? 'back' : 'forward';
this.lastNavId = this.guessDirection === 'forward' ? ev.id : id;
}
});
Expand Down Expand Up @@ -180,11 +180,14 @@ export class NavController {
direction: RouterDirection;
animation: NavDirection | undefined;
animationBuilder: AnimationBuilder | undefined;
isDirectionBasedOnNavigationIds: boolean;
} {
let direction: RouterDirection = 'root';
let animation: NavDirection | undefined;
const animationBuilder = this.animationBuilder;

const isDirectionBasedOnNavigationIds = this.direction === 'auto';

if (this.direction === 'auto') {
direction = this.guessDirection;
animation = this.guessAnimation;
Expand All @@ -200,6 +203,7 @@ export class NavController {
direction,
animation,
animationBuilder,
isDirectionBasedOnNavigationIds,
};
}

Expand Down