mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-21 04:53:58 +08:00
255 lines
8.3 KiB
TypeScript
255 lines
8.3 KiB
TypeScript
import { Location } from '@angular/common';
|
|
import { Injectable, Optional } from '@angular/core';
|
|
import { NavigationExtras, Router, UrlSerializer, UrlTree, NavigationStart } from '@angular/router';
|
|
import { AnimationBuilder, NavDirection, RouterDirection } from '@ionic/core';
|
|
|
|
import { IonRouterOutlet } from '../directives/navigation/ion-router-outlet';
|
|
|
|
import { Platform } from './platform';
|
|
|
|
export interface AnimationOptions {
|
|
animated?: boolean;
|
|
animation?: AnimationBuilder;
|
|
animationDirection?: 'forward' | 'back';
|
|
}
|
|
|
|
export interface NavigationOptions extends NavigationExtras, AnimationOptions {}
|
|
|
|
@Injectable({
|
|
providedIn: 'root',
|
|
})
|
|
export class NavController {
|
|
private topOutlet?: IonRouterOutlet;
|
|
private direction: 'forward' | 'back' | 'root' | 'auto' = DEFAULT_DIRECTION;
|
|
private animated?: NavDirection = DEFAULT_ANIMATED;
|
|
private animationBuilder?: AnimationBuilder;
|
|
private guessDirection: RouterDirection = 'forward';
|
|
private guessAnimation?: NavDirection;
|
|
private lastNavId = -1;
|
|
|
|
constructor(
|
|
platform: Platform,
|
|
private location: Location,
|
|
private serializer: UrlSerializer,
|
|
@Optional() private router?: Router
|
|
) {
|
|
// Subscribe to router events to detect direction
|
|
if (router) {
|
|
router.events.subscribe((ev) => {
|
|
if (ev instanceof NavigationStart) {
|
|
const id = ev.restoredState ? ev.restoredState.navigationId : ev.id;
|
|
this.guessDirection = id < this.lastNavId ? 'back' : 'forward';
|
|
this.guessAnimation = !ev.restoredState ? this.guessDirection : undefined;
|
|
this.lastNavId = this.guessDirection === 'forward' ? ev.id : id;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Subscribe to backButton events
|
|
platform.backButton.subscribeWithPriority(0, (processNextHandler) => {
|
|
this.pop();
|
|
processNextHandler();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* This method uses Angular's [Router](https://angular.io/api/router/Router) under the hood,
|
|
* it's equivalent to calling `this.router.navigateByUrl()`, but it's explicit about the **direction** of the transition.
|
|
*
|
|
* Going **forward** means that a new page is going to be pushed to the stack of the outlet (ion-router-outlet),
|
|
* and that it will show a "forward" animation by default.
|
|
*
|
|
* Navigating forward can also be triggered in a declarative manner by using the `[routerDirection]` directive:
|
|
*
|
|
* ```html
|
|
* <a routerLink="/path/to/page" routerDirection="forward">Link</a>
|
|
* ```
|
|
*/
|
|
navigateForward(url: string | UrlTree | any[], options: NavigationOptions = {}): Promise<boolean> {
|
|
this.setDirection('forward', options.animated, options.animationDirection, options.animation);
|
|
return this.navigate(url, options);
|
|
}
|
|
|
|
/**
|
|
* This method uses Angular's [Router](https://angular.io/api/router/Router) under the hood,
|
|
* it's equivalent to calling:
|
|
*
|
|
* ```ts
|
|
* this.navController.setDirection('back');
|
|
* this.router.navigateByUrl(path);
|
|
* ```
|
|
*
|
|
* Going **back** means that all the pages in the stack until the navigated page is found will be popped,
|
|
* and that it will show a "back" animation by default.
|
|
*
|
|
* Navigating back can also be triggered in a declarative manner by using the `[routerDirection]` directive:
|
|
*
|
|
* ```html
|
|
* <a routerLink="/path/to/page" routerDirection="back">Link</a>
|
|
* ```
|
|
*/
|
|
navigateBack(url: string | UrlTree | any[], options: NavigationOptions = {}): Promise<boolean> {
|
|
this.setDirection('back', options.animated, options.animationDirection, options.animation);
|
|
return this.navigate(url, options);
|
|
}
|
|
|
|
/**
|
|
* This method uses Angular's [Router](https://angular.io/api/router/Router) under the hood,
|
|
* it's equivalent to calling:
|
|
*
|
|
* ```ts
|
|
* this.navController.setDirection('root');
|
|
* this.router.navigateByUrl(path);
|
|
* ```
|
|
*
|
|
* Going **root** means that all existing pages in the stack will be removed,
|
|
* and the navigated page will become the single page in the stack.
|
|
*
|
|
* Navigating root can also be triggered in a declarative manner by using the `[routerDirection]` directive:
|
|
*
|
|
* ```html
|
|
* <a routerLink="/path/to/page" routerDirection="root">Link</a>
|
|
* ```
|
|
*/
|
|
navigateRoot(url: string | UrlTree | any[], options: NavigationOptions = {}): Promise<boolean> {
|
|
this.setDirection('root', options.animated, options.animationDirection, options.animation);
|
|
return this.navigate(url, options);
|
|
}
|
|
|
|
/**
|
|
* Same as [Location](https://angular.io/api/common/Location)'s back() method.
|
|
* It will use the standard `window.history.back()` under the hood, but featuring a `back` animation
|
|
* by default.
|
|
*/
|
|
back(options: AnimationOptions = { animated: true, animationDirection: 'back' }): void {
|
|
this.setDirection('back', options.animated, options.animationDirection, options.animation);
|
|
return this.location.back();
|
|
}
|
|
|
|
/**
|
|
* This methods goes back in the context of Ionic's stack navigation.
|
|
*
|
|
* It recursively finds the top active `ion-router-outlet` and calls `pop()`.
|
|
* This is the recommended way to go back when you are using `ion-router-outlet`.
|
|
*/
|
|
async pop(): Promise<void> {
|
|
let outlet = this.topOutlet;
|
|
|
|
while (outlet) {
|
|
if (await outlet.pop()) {
|
|
break;
|
|
} else {
|
|
outlet = outlet.parentOutlet;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This methods specifies the direction of the next navigation performed by the Angular router.
|
|
*
|
|
* `setDirection()` does not trigger any transition, it just sets some flags to be consumed by `ion-router-outlet`.
|
|
*
|
|
* It's recommended to use `navigateForward()`, `navigateBack()` and `navigateRoot()` instead of `setDirection()`.
|
|
*/
|
|
setDirection(
|
|
direction: RouterDirection,
|
|
animated?: boolean,
|
|
animationDirection?: 'forward' | 'back',
|
|
animationBuilder?: AnimationBuilder
|
|
): void {
|
|
this.direction = direction;
|
|
this.animated = getAnimation(direction, animated, animationDirection);
|
|
this.animationBuilder = animationBuilder;
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
setTopOutlet(outlet: IonRouterOutlet): void {
|
|
this.topOutlet = outlet;
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
consumeTransition(): {
|
|
direction: RouterDirection;
|
|
animation: NavDirection | undefined;
|
|
animationBuilder: AnimationBuilder | undefined;
|
|
} {
|
|
let direction: RouterDirection = 'root';
|
|
let animation: NavDirection | undefined;
|
|
const animationBuilder = this.animationBuilder;
|
|
|
|
if (this.direction === 'auto') {
|
|
direction = this.guessDirection;
|
|
animation = this.guessAnimation;
|
|
} else {
|
|
animation = this.animated;
|
|
direction = this.direction;
|
|
}
|
|
this.direction = DEFAULT_DIRECTION;
|
|
this.animated = DEFAULT_ANIMATED;
|
|
this.animationBuilder = undefined;
|
|
|
|
return {
|
|
direction,
|
|
animation,
|
|
animationBuilder,
|
|
};
|
|
}
|
|
|
|
private navigate(url: string | UrlTree | any[], options: NavigationOptions) {
|
|
if (Array.isArray(url)) {
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
return this.router!.navigate(url, options);
|
|
} else {
|
|
/**
|
|
* navigateByUrl ignores any properties that
|
|
* would change the url, so things like queryParams
|
|
* would be ignored unless we create a url tree
|
|
* More Info: https://github.com/angular/angular/issues/18798
|
|
*/
|
|
const urlTree = this.serializer.parse(url.toString());
|
|
|
|
if (options.queryParams !== undefined) {
|
|
urlTree.queryParams = { ...options.queryParams };
|
|
}
|
|
|
|
if (options.fragment !== undefined) {
|
|
urlTree.fragment = options.fragment;
|
|
}
|
|
|
|
/**
|
|
* `navigateByUrl` will still apply `NavigationExtras` properties
|
|
* that do not modify the url, such as `replaceUrl` which is why
|
|
* `options` is passed in here.
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
return this.router!.navigateByUrl(urlTree, options);
|
|
}
|
|
}
|
|
}
|
|
|
|
const getAnimation = (
|
|
direction: RouterDirection,
|
|
animated: boolean | undefined,
|
|
animationDirection: 'forward' | 'back' | undefined
|
|
): NavDirection | undefined => {
|
|
if (animated === false) {
|
|
return undefined;
|
|
}
|
|
if (animationDirection !== undefined) {
|
|
return animationDirection;
|
|
}
|
|
if (direction === 'forward' || direction === 'back') {
|
|
return direction;
|
|
} else if (direction === 'root' && animated === true) {
|
|
return 'forward';
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
const DEFAULT_DIRECTION = 'auto';
|
|
const DEFAULT_ANIMATED = undefined;
|