import { Location } from '@angular/common'; import { App } from '../components/app/app'; import { convertToViews, isNav, isTab, isTabs, NavSegment, DIRECTION_BACK } from './nav-util'; import { isArray, isPresent } from '../util/util'; import { Nav } from '../components/nav/nav'; import { NavController } from './nav-controller'; import { Tab } from '../components/tabs/tab'; import { Tabs } from '../components/tabs/tabs'; import { UrlSerializer } from './url-serializer'; import { ViewController } from './view-controller'; /** * @name DeepLinker * @description * DeepLinker handles registering and displaying specific views based on URLs. It's used * underneath NavController so you'll never have to interact with it directly. When a new * view is push'ed with NavController, the URL is updated to match the path back to this * page. * * Unlike traditional web apps, URLs don't dictate navigation in Ionic apps. * Instead, URLs help us link to specific pieces of content as a breadcrumb. * We keep the current URL updated as we navigate, but we use the NavController's * push and pop, or navPush to move around. This makes it much easier * to handle the kinds of complicated nested navigation native apps are known for. * * We refer to our URL system as a Deep Link system instead of a Router to encourage * Ionic developers to think of URLs as a breadcrumb rather than as the source of * truth in navigation. This encourages flexible navigation design and happy apps all * over the world. * * * @usage * * DeepLinker can be used in the `IonicModule.forRoot` method, as the third parameter * * ```ts * imports: [ * IonicModule.forRoot(MyApp, {}, { * links: [] * }) * ] * ``` * * DeepLinker implements `DeepLinkerConfig`, which is an object with an array of links. * So for basic example based on the blank starer, a link setup like so: * * ```ts * imports: [ * IonicModule.forRoot(MyApp, {}, { * links: [ * { component: HomePage, name: 'Home', segment: 'home' } * ] * }) * ] * ``` * * This Feels pretty familiar to how Angular sets up routes, but has some fundamental differences. * Since components could be loaded anywhere in the app, DeepLinker lets you define their URL segment. * So at any point, when a Component becomes the active view, we just append the URL segment. * * ### Dynamic Links * * Since passing data around is common practice in an app, we can reflect that in our app's URL in a similar manner to Angular's router. * * ```ts * links: [ * { component: HomePage, name: 'Home', segment: 'home' }, * { component: DetailPage, name: 'Detail', segment: 'detail/:user' } * ] * ``` * This approach of using `:param` has been around in previous routing solutions. * All this means is that when we push a new component on to the stack, in the navParams, there should be a property of `user`. * The property needs to be something that can be serialized by the DeepLinker. * So setting its value to be that of a string or number is suggested. * * So in a typical `navCtrl.push()` scenario, we'd do something like this: * * ```ts * pushPage(userInfo) { * this.navCtrl.push(DetailPage, { * 'user': userInfo * }) * } * ``` * * * * ### Default History * * In some cases when a page loads, you might be sent to a component that has it's own information, but not back view. * This situation is common when loading a page from a Push Notification. * If you want a component to have a default history when none is present, you can use the `defaultHistory` property * * The `defaultHistory` property takes an array of components to create the history stack if none exist. * * ```ts * links: [ * { component: HomePage, name: 'Home', segment: 'home' }, * { component: DetailPage, name: 'Detail', segment: 'detail/:user', defaultHistory: [HomePage] } * ] * ``` */ export class DeepLinker { /** * @internal */ segments: NavSegment[] = []; /** * @internal */ history: string[] = []; /** * @internal */ indexAliasUrl: string; constructor(public _app: App, public _serializer: UrlSerializer, public _location: Location) { } /** * @internal */ init() { // scenario 1: Initial load of all navs from the initial browser URL const browserUrl = normalizeUrl(this._location.path()); console.debug(`DeepLinker, init load: ${browserUrl}`); // update the Path from the browser URL this.segments = this._serializer.parse(browserUrl); // remember this URL in our internal history stack this.historyPush(browserUrl); // listen for browser URL changes this._location.subscribe((locationChg: { url: string }) => { this.urlChange(normalizeUrl(locationChg.url)); }); } /** * The browser's location has been updated somehow. * @internal */ urlChange(browserUrl: string) { // do nothing if this url is the same as the current one if (!this.isCurrentUrl(browserUrl)) { if (this.isBackUrl(browserUrl)) { // scenario 2: user clicked the browser back button // scenario 4: user changed the browser URL to what was the back url was // scenario 5: user clicked a link href that was the back url console.debug(`DeepLinker, browser urlChange, back to: ${browserUrl}`); this.historyPop(); } else { // scenario 3: user click forward button // scenario 4: user changed browser URL that wasn't the back url // scenario 5: user clicked a link href that wasn't the back url console.debug(`DeepLinker, browser urlChange, forward to: ${browserUrl}`); this.historyPush(browserUrl); } // get the app's root nav const appRootNav =