UNPKG

@angular/router

Version:
806 lines 112 kB
/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import { Location } from '@angular/common'; import { inject, Injectable, NgZone, ɵConsole as Console, ɵInitialRenderPendingTasks as InitialRenderPendingTasks, ɵRuntimeError as RuntimeError } from '@angular/core'; import { Subject, Subscription } from 'rxjs'; import { createSegmentGroupFromRoute, createUrlTreeFromSegmentGroup } from './create_url_tree'; import { INPUT_BINDER } from './directives/router_outlet'; import { BeforeActivateRoutes, IMPERATIVE_NAVIGATION, NavigationCancel, NavigationEnd, NavigationError, NavigationSkipped, NavigationStart, RedirectRequest, RoutesRecognized } from './events'; import { isBrowserTriggeredNavigation, NavigationTransitions } from './navigation_transition'; import { TitleStrategy } from './page_title_strategy'; import { RouteReuseStrategy } from './route_reuse_strategy'; import { ROUTER_CONFIGURATION } from './router_config'; import { ROUTES } from './router_config_loader'; import { createEmptyState } from './router_state'; import { UrlHandlingStrategy } from './url_handling_strategy'; import { containsTree, isUrlTree, UrlSerializer, UrlTree } from './url_tree'; import { standardizeConfig, validateConfig } from './utils/config'; import { afterNextNavigation } from './utils/navigations'; import * as i0 from "@angular/core"; function defaultErrorHandler(error) { throw error; } function defaultMalformedUriErrorHandler(error, urlSerializer, url) { return urlSerializer.parse('/'); } /** * The equivalent `IsActiveMatchOptions` options for `Router.isActive` is called with `true` * (exact = true). */ export const exactMatchOptions = { paths: 'exact', fragment: 'ignored', matrixParams: 'ignored', queryParams: 'exact' }; /** * The equivalent `IsActiveMatchOptions` options for `Router.isActive` is called with `false` * (exact = false). */ export const subsetMatchOptions = { paths: 'subset', fragment: 'ignored', matrixParams: 'ignored', queryParams: 'subset' }; /** * @description * * A service that provides navigation among views and URL manipulation capabilities. * * @see {@link Route} * @see [Routing and Navigation Guide](guide/router). * * @ngModule RouterModule * * @publicApi */ export class Router { // TODO(b/260747083): This should not exist and navigationId should be private in // `NavigationTransitions` get navigationId() { return this.navigationTransitions.navigationId; } /** * The ɵrouterPageId of whatever page is currently active in the browser history. This is * important for computing the target page id for new navigations because we need to ensure each * page id in the browser history is 1 more than the previous entry. */ get browserPageId() { if (this.canceledNavigationResolution !== 'computed') { return this.currentPageId; } return this.location.getState()?.ɵrouterPageId ?? this.currentPageId; } /** * An event stream for routing events. */ get events() { // TODO(atscott): This _should_ be events.asObservable(). However, this change requires internal // cleanup: tests are doing `(route.events as Subject<Event>).next(...)`. This isn't // allowed/supported but we still have to fix these or file bugs against the teams before making // the change. return this._events; } constructor() { this.disposed = false; /** * The id of the currently active page in the router. * Updated to the transition's target id on a successful navigation. * * This is used to track what page the router last activated. When an attempted navigation fails, * the router can then use this to compute how to restore the state back to the previously active * page. */ this.currentPageId = 0; this.console = inject(Console); this.isNgZoneEnabled = false; /** * The private `Subject` type for the public events exposed in the getter. This is used internally * to push events to. The separate field allows us to expose separate types in the public API * (i.e., an Observable rather than the Subject). */ this._events = new Subject(); this.options = inject(ROUTER_CONFIGURATION, { optional: true }) || {}; this.pendingTasks = inject(InitialRenderPendingTasks); /** * A handler for navigation errors in this NgModule. * * @deprecated Subscribe to the `Router` events and watch for `NavigationError` instead. * `provideRouter` has the `withNavigationErrorHandler` feature to make this easier. * @see {@link withNavigationErrorHandler} */ this.errorHandler = this.options.errorHandler || defaultErrorHandler; /** * A handler for errors thrown by `Router.parseUrl(url)` * when `url` contains an invalid character. * The most common case is a `%` sign * that's not encoded and is not part of a percent encoded sequence. * * @deprecated URI parsing errors should be handled in the `UrlSerializer`. * * @see {@link RouterModule} */ this.malformedUriErrorHandler = this.options.malformedUriErrorHandler || defaultMalformedUriErrorHandler; /** * True if at least one navigation event has occurred, * false otherwise. */ this.navigated = false; this.lastSuccessfulId = -1; /** * A strategy for extracting and merging URLs. * Used for AngularJS to Angular migrations. * * @deprecated Configure using `providers` instead: * `{provide: UrlHandlingStrategy, useClass: MyStrategy}`. */ this.urlHandlingStrategy = inject(UrlHandlingStrategy); /** * A strategy for re-using routes. * * @deprecated Configure using `providers` instead: * `{provide: RouteReuseStrategy, useClass: MyStrategy}`. */ this.routeReuseStrategy = inject(RouteReuseStrategy); /** * A strategy for setting the title based on the `routerState`. * * @deprecated Configure using `providers` instead: * `{provide: TitleStrategy, useClass: MyStrategy}`. */ this.titleStrategy = inject(TitleStrategy); /** * How to handle a navigation request to the current URL. * * * @deprecated Configure this through `provideRouter` or `RouterModule.forRoot` instead. * @see {@link withRouterConfig} * @see {@link provideRouter} * @see {@link RouterModule} */ this.onSameUrlNavigation = this.options.onSameUrlNavigation || 'ignore'; /** * How to merge parameters, data, resolved data, and title from parent to child * routes. One of: * * - `'emptyOnly'` : Inherit parent parameters, data, and resolved data * for path-less or component-less routes. * - `'always'` : Inherit parent parameters, data, and resolved data * for all child routes. * * @deprecated Configure this through `provideRouter` or `RouterModule.forRoot` instead. * @see {@link withRouterConfig} * @see {@link provideRouter} * @see {@link RouterModule} */ this.paramsInheritanceStrategy = this.options.paramsInheritanceStrategy || 'emptyOnly'; /** * Determines when the router updates the browser URL. * By default (`"deferred"`), updates the browser URL after navigation has finished. * Set to `'eager'` to update the browser URL at the beginning of navigation. * You can choose to update early so that, if navigation fails, * you can show an error message with the URL that failed. * * @deprecated Configure this through `provideRouter` or `RouterModule.forRoot` instead. * @see {@link withRouterConfig} * @see {@link provideRouter} * @see {@link RouterModule} */ this.urlUpdateStrategy = this.options.urlUpdateStrategy || 'deferred'; /** * Configures how the Router attempts to restore state when a navigation is cancelled. * * 'replace' - Always uses `location.replaceState` to set the browser state to the state of the * router before the navigation started. This means that if the URL of the browser is updated * _before_ the navigation is canceled, the Router will simply replace the item in history rather * than trying to restore to the previous location in the session history. This happens most * frequently with `urlUpdateStrategy: 'eager'` and navigations with the browser back/forward * buttons. * * 'computed' - Will attempt to return to the same index in the session history that corresponds * to the Angular route when the navigation gets cancelled. For example, if the browser back * button is clicked and the navigation is cancelled, the Router will trigger a forward navigation * and vice versa. * * Note: the 'computed' option is incompatible with any `UrlHandlingStrategy` which only * handles a portion of the URL because the history restoration navigates to the previous place in * the browser history rather than simply resetting a portion of the URL. * * The default value is `replace`. * * @deprecated Configure this through `provideRouter` or `RouterModule.forRoot` instead. * @see {@link withRouterConfig} * @see {@link provideRouter} * @see {@link RouterModule} */ this.canceledNavigationResolution = this.options.canceledNavigationResolution || 'replace'; this.config = inject(ROUTES, { optional: true })?.flat() ?? []; this.navigationTransitions = inject(NavigationTransitions); this.urlSerializer = inject(UrlSerializer); this.location = inject(Location); /** * Indicates whether the application has opted in to binding Router data to component inputs. * * This option is enabled by the `withComponentInputBinding` feature of `provideRouter` or * `bindToComponentInputs` in the `ExtraOptions` of `RouterModule.forRoot`. */ this.componentInputBindingEnabled = !!inject(INPUT_BINDER, { optional: true }); this.eventsSubscription = new Subscription(); this.isNgZoneEnabled = inject(NgZone) instanceof NgZone && NgZone.isInAngularZone(); this.resetConfig(this.config); this.currentUrlTree = new UrlTree(); this.rawUrlTree = this.currentUrlTree; this.browserUrlTree = this.currentUrlTree; this.routerState = createEmptyState(this.currentUrlTree, null); this.navigationTransitions.setupNavigations(this, this.currentUrlTree, this.routerState) .subscribe(t => { this.lastSuccessfulId = t.id; this.currentPageId = this.browserPageId; }, e => { this.console.warn(`Unhandled Navigation Error: ${e}`); }); this.subscribeToNavigationEvents(); } subscribeToNavigationEvents() { const subscription = this.navigationTransitions.events.subscribe(e => { try { const { currentTransition } = this.navigationTransitions; if (currentTransition === null) { if (isPublicRouterEvent(e)) { this._events.next(e); } return; } if (e instanceof NavigationStart) { // If the source of the navigation is from a browser event, the URL is // already updated. We already need to sync the internal state. if (isBrowserTriggeredNavigation(currentTransition.source)) { this.browserUrlTree = currentTransition.extractedUrl; } } else if (e instanceof NavigationSkipped) { this.rawUrlTree = currentTransition.rawUrl; } else if (e instanceof RoutesRecognized) { if (this.urlUpdateStrategy === 'eager') { if (!currentTransition.extras.skipLocationChange) { const rawUrl = this.urlHandlingStrategy.merge(currentTransition.urlAfterRedirects, currentTransition.rawUrl); this.setBrowserUrl(rawUrl, currentTransition); } this.browserUrlTree = currentTransition.urlAfterRedirects; } } else if (e instanceof BeforeActivateRoutes) { this.currentUrlTree = currentTransition.urlAfterRedirects; this.rawUrlTree = this.urlHandlingStrategy.merge(currentTransition.urlAfterRedirects, currentTransition.rawUrl); this.routerState = currentTransition.targetRouterState; if (this.urlUpdateStrategy === 'deferred') { if (!currentTransition.extras.skipLocationChange) { this.setBrowserUrl(this.rawUrlTree, currentTransition); } this.browserUrlTree = currentTransition.urlAfterRedirects; } } else if (e instanceof NavigationCancel) { if (e.code !== 0 /* NavigationCancellationCode.Redirect */ && e.code !== 1 /* NavigationCancellationCode.SupersededByNewNavigation */) { // It seems weird that `navigated` is set to `true` when the navigation is rejected, // however it's how things were written initially. Investigation would need to be done // to determine if this can be removed. this.navigated = true; } if (e.code === 3 /* NavigationCancellationCode.GuardRejected */ || e.code === 2 /* NavigationCancellationCode.NoDataFromResolver */) { this.restoreHistory(currentTransition); } } else if (e instanceof RedirectRequest) { const mergedTree = this.urlHandlingStrategy.merge(e.url, currentTransition.currentRawUrl); const extras = { skipLocationChange: currentTransition.extras.skipLocationChange, // The URL is already updated at this point if we have 'eager' URL // updates or if the navigation was triggered by the browser (back // button, URL bar, etc). We want to replace that item in history // if the navigation is rejected. replaceUrl: this.urlUpdateStrategy === 'eager' || isBrowserTriggeredNavigation(currentTransition.source) }; this.scheduleNavigation(mergedTree, IMPERATIVE_NAVIGATION, null, extras, { resolve: currentTransition.resolve, reject: currentTransition.reject, promise: currentTransition.promise }); } if (e instanceof NavigationError) { this.restoreHistory(currentTransition, true); } if (e instanceof NavigationEnd) { this.navigated = true; } // Note that it's important to have the Router process the events _before_ the event is // pushed through the public observable. This ensures the correct router state is in place // before applications observe the events. if (isPublicRouterEvent(e)) { this._events.next(e); } } catch (e) { this.navigationTransitions.transitionAbortSubject.next(e); } }); this.eventsSubscription.add(subscription); } /** @internal */ resetRootComponentType(rootComponentType) { // TODO: vsavkin router 4.0 should make the root component set to null // this will simplify the lifecycle of the router. this.routerState.root.component = rootComponentType; this.navigationTransitions.rootComponentType = rootComponentType; } /** * Sets up the location change listener and performs the initial navigation. */ initialNavigation() { this.setUpLocationChangeListener(); if (!this.navigationTransitions.hasRequestedNavigation) { const state = this.location.getState(); this.navigateToSyncWithBrowser(this.location.path(true), IMPERATIVE_NAVIGATION, state); } } /** * Sets up the location change listener. This listener detects navigations triggered from outside * the Router (the browser back/forward buttons, for example) and schedules a corresponding Router * navigation so that the correct events, guards, etc. are triggered. */ setUpLocationChangeListener() { // Don't need to use Zone.wrap any more, because zone.js // already patch onPopState, so location change callback will // run into ngZone if (!this.locationSubscription) { this.locationSubscription = this.location.subscribe(event => { const source = event['type'] === 'popstate' ? 'popstate' : 'hashchange'; if (source === 'popstate') { // The `setTimeout` was added in #12160 and is likely to support Angular/AngularJS // hybrid apps. setTimeout(() => { this.navigateToSyncWithBrowser(event['url'], source, event.state); }, 0); } }); } } /** * Schedules a router navigation to synchronize Router state with the browser state. * * This is done as a response to a popstate event and the initial navigation. These * two scenarios represent times when the browser URL/state has been updated and * the Router needs to respond to ensure its internal state matches. */ navigateToSyncWithBrowser(url, source, state) { const extras = { replaceUrl: true }; // TODO: restoredState should always include the entire state, regardless // of navigationId. This requires a breaking change to update the type on // NavigationStart’s restoredState, which currently requires navigationId // to always be present. The Router used to only restore history state if // a navigationId was present. // The stored navigationId is used by the RouterScroller to retrieve the scroll // position for the page. const restoredState = state?.navigationId ? state : null; // Separate to NavigationStart.restoredState, we must also restore the state to // history.state and generate a new navigationId, since it will be overwritten if (state) { const stateCopy = { ...state }; delete stateCopy.navigationId; delete stateCopy.ɵrouterPageId; if (Object.keys(stateCopy).length !== 0) { extras.state = stateCopy; } } const urlTree = this.parseUrl(url); this.scheduleNavigation(urlTree, source, restoredState, extras); } /** The current URL. */ get url() { return this.serializeUrl(this.currentUrlTree); } /** * Returns the current `Navigation` object when the router is navigating, * and `null` when idle. */ getCurrentNavigation() { return this.navigationTransitions.currentNavigation; } /** * The `Navigation` object of the most recent navigation to succeed and `null` if there * has not been a successful navigation yet. */ get lastSuccessfulNavigation() { return this.navigationTransitions.lastSuccessfulNavigation; } /** * Resets the route configuration used for navigation and generating links. * * @param config The route array for the new configuration. * * @usageNotes * * ``` * router.resetConfig([ * { path: 'team/:id', component: TeamCmp, children: [ * { path: 'simple', component: SimpleCmp }, * { path: 'user/:name', component: UserCmp } * ]} * ]); * ``` */ resetConfig(config) { (typeof ngDevMode === 'undefined' || ngDevMode) && validateConfig(config); this.config = config.map(standardizeConfig); this.navigated = false; this.lastSuccessfulId = -1; } /** @nodoc */ ngOnDestroy() { this.dispose(); } /** Disposes of the router. */ dispose() { this.navigationTransitions.complete(); if (this.locationSubscription) { this.locationSubscription.unsubscribe(); this.locationSubscription = undefined; } this.disposed = true; this.eventsSubscription.unsubscribe(); } /** * Appends URL segments to the current URL tree to create a new URL tree. * * @param commands An array of URL fragments with which to construct the new URL tree. * If the path is static, can be the literal URL string. For a dynamic path, pass an array of path * segments, followed by the parameters for each segment. * The fragments are applied to the current URL tree or the one provided in the `relativeTo` * property of the options object, if supplied. * @param navigationExtras Options that control the navigation strategy. * @returns The new URL tree. * * @usageNotes * * ``` * // create /team/33/user/11 * router.createUrlTree(['/team', 33, 'user', 11]); * * // create /team/33;expand=true/user/11 * router.createUrlTree(['/team', 33, {expand: true}, 'user', 11]); * * // you can collapse static segments like this (this works only with the first passed-in value): * router.createUrlTree(['/team/33/user', userId]); * * // If the first segment can contain slashes, and you do not want the router to split it, * // you can do the following: * router.createUrlTree([{segmentPath: '/one/two'}]); * * // create /team/33/(user/11//right:chat) * router.createUrlTree(['/team', 33, {outlets: {primary: 'user/11', right: 'chat'}}]); * * // remove the right secondary node * router.createUrlTree(['/team', 33, {outlets: {primary: 'user/11', right: null}}]); * * // assuming the current url is `/team/33/user/11` and the route points to `user/11` * * // navigate to /team/33/user/11/details * router.createUrlTree(['details'], {relativeTo: route}); * * // navigate to /team/33/user/22 * router.createUrlTree(['../22'], {relativeTo: route}); * * // navigate to /team/44/user/22 * router.createUrlTree(['../../team/44/user/22'], {relativeTo: route}); * * Note that a value of `null` or `undefined` for `relativeTo` indicates that the * tree should be created relative to the root. * ``` */ createUrlTree(commands, navigationExtras = {}) { const { relativeTo, queryParams, fragment, queryParamsHandling, preserveFragment } = navigationExtras; const f = preserveFragment ? this.currentUrlTree.fragment : fragment; let q = null; switch (queryParamsHandling) { case 'merge': q = { ...this.currentUrlTree.queryParams, ...queryParams }; break; case 'preserve': q = this.currentUrlTree.queryParams; break; default: q = queryParams || null; } if (q !== null) { q = this.removeEmptyProps(q); } let relativeToUrlSegmentGroup; try { const relativeToSnapshot = relativeTo ? relativeTo.snapshot : this.routerState.snapshot.root; relativeToUrlSegmentGroup = createSegmentGroupFromRoute(relativeToSnapshot); } catch (e) { // This is strictly for backwards compatibility with tests that create // invalid `ActivatedRoute` mocks. // Note: the difference between having this fallback for invalid `ActivatedRoute` setups and // just throwing is ~500 test failures. Fixing all of those tests by hand is not feasible at // the moment. if (typeof commands[0] !== 'string' || !commands[0].startsWith('/')) { // Navigations that were absolute in the old way of creating UrlTrees // would still work because they wouldn't attempt to match the // segments in the `ActivatedRoute` to the `currentUrlTree` but // instead just replace the root segment with the navigation result. // Non-absolute navigations would fail to apply the commands because // the logic could not find the segment to replace (so they'd act like there were no // commands). commands = []; } relativeToUrlSegmentGroup = this.currentUrlTree.root; } return createUrlTreeFromSegmentGroup(relativeToUrlSegmentGroup, commands, q, f ?? null); } /** * Navigates to a view using an absolute route path. * * @param url An absolute path for a defined route. The function does not apply any delta to the * current URL. * @param extras An object containing properties that modify the navigation strategy. * * @returns A Promise that resolves to 'true' when navigation succeeds, * to 'false' when navigation fails, or is rejected on error. * * @usageNotes * * The following calls request navigation to an absolute path. * * ``` * router.navigateByUrl("/team/33/user/11"); * * // Navigate without updating the URL * router.navigateByUrl("/team/33/user/11", { skipLocationChange: true }); * ``` * * @see [Routing and Navigation guide](guide/router) * */ navigateByUrl(url, extras = { skipLocationChange: false }) { if (typeof ngDevMode === 'undefined' || ngDevMode) { if (this.isNgZoneEnabled && !NgZone.isInAngularZone()) { this.console.warn(`Navigation triggered outside Angular zone, did you forget to call 'ngZone.run()'?`); } } const urlTree = isUrlTree(url) ? url : this.parseUrl(url); const mergedTree = this.urlHandlingStrategy.merge(urlTree, this.rawUrlTree); return this.scheduleNavigation(mergedTree, IMPERATIVE_NAVIGATION, null, extras); } /** * Navigate based on the provided array of commands and a starting point. * If no starting route is provided, the navigation is absolute. * * @param commands An array of URL fragments with which to construct the target URL. * If the path is static, can be the literal URL string. For a dynamic path, pass an array of path * segments, followed by the parameters for each segment. * The fragments are applied to the current URL or the one provided in the `relativeTo` property * of the options object, if supplied. * @param extras An options object that determines how the URL should be constructed or * interpreted. * * @returns A Promise that resolves to `true` when navigation succeeds, to `false` when navigation * fails, * or is rejected on error. * * @usageNotes * * The following calls request navigation to a dynamic route path relative to the current URL. * * ``` * router.navigate(['team', 33, 'user', 11], {relativeTo: route}); * * // Navigate without updating the URL, overriding the default behavior * router.navigate(['team', 33, 'user', 11], {relativeTo: route, skipLocationChange: true}); * ``` * * @see [Routing and Navigation guide](guide/router) * */ navigate(commands, extras = { skipLocationChange: false }) { validateCommands(commands); return this.navigateByUrl(this.createUrlTree(commands, extras), extras); } /** Serializes a `UrlTree` into a string */ serializeUrl(url) { return this.urlSerializer.serialize(url); } /** Parses a string into a `UrlTree` */ parseUrl(url) { let urlTree; try { urlTree = this.urlSerializer.parse(url); } catch (e) { urlTree = this.malformedUriErrorHandler(e, this.urlSerializer, url); } return urlTree; } isActive(url, matchOptions) { let options; if (matchOptions === true) { options = { ...exactMatchOptions }; } else if (matchOptions === false) { options = { ...subsetMatchOptions }; } else { options = matchOptions; } if (isUrlTree(url)) { return containsTree(this.currentUrlTree, url, options); } const urlTree = this.parseUrl(url); return containsTree(this.currentUrlTree, urlTree, options); } removeEmptyProps(params) { return Object.keys(params).reduce((result, key) => { const value = params[key]; if (value !== null && value !== undefined) { result[key] = value; } return result; }, {}); } /** @internal */ scheduleNavigation(rawUrl, source, restoredState, extras, priorPromise) { if (this.disposed) { return Promise.resolve(false); } let resolve; let reject; let promise; if (priorPromise) { resolve = priorPromise.resolve; reject = priorPromise.reject; promise = priorPromise.promise; } else { promise = new Promise((res, rej) => { resolve = res; reject = rej; }); } // Indicate that the navigation is happening. const taskId = this.pendingTasks.add(); afterNextNavigation(this, () => { // Remove pending task in a microtask to allow for cancelled // initial navigations and redirects within the same task. queueMicrotask(() => this.pendingTasks.remove(taskId)); }); this.navigationTransitions.handleNavigationRequest({ source, restoredState, currentUrlTree: this.currentUrlTree, currentRawUrl: this.currentUrlTree, currentBrowserUrl: this.browserUrlTree, rawUrl, extras, resolve, reject, promise, currentSnapshot: this.routerState.snapshot, currentRouterState: this.routerState }); // Make sure that the error is propagated even though `processNavigations` catch // handler does not rethrow return promise.catch((e) => { return Promise.reject(e); }); } /** @internal */ setBrowserUrl(url, transition) { const path = this.urlSerializer.serialize(url); if (this.location.isCurrentPathEqualTo(path) || !!transition.extras.replaceUrl) { // replacements do not update the target page const currentBrowserPageId = this.browserPageId; const state = { ...transition.extras.state, ...this.generateNgRouterState(transition.id, currentBrowserPageId) }; this.location.replaceState(path, '', state); } else { const state = { ...transition.extras.state, ...this.generateNgRouterState(transition.id, this.browserPageId + 1) }; this.location.go(path, '', state); } } /** * Performs the necessary rollback action to restore the browser URL to the * state before the transition. * @internal */ restoreHistory(transition, restoringFromCaughtError = false) { if (this.canceledNavigationResolution === 'computed') { const currentBrowserPageId = this.browserPageId; const targetPagePosition = this.currentPageId - currentBrowserPageId; if (targetPagePosition !== 0) { this.location.historyGo(targetPagePosition); } else if (this.currentUrlTree === this.getCurrentNavigation()?.finalUrl && targetPagePosition === 0) { // We got to the activation stage (where currentUrlTree is set to the navigation's // finalUrl), but we weren't moving anywhere in history (skipLocationChange or replaceUrl). // We still need to reset the router state back to what it was when the navigation started. this.resetState(transition); // TODO(atscott): resetting the `browserUrlTree` should really be done in `resetState`. // Investigate if this can be done by running TGP. this.browserUrlTree = transition.currentUrlTree; this.resetUrlToCurrentUrlTree(); } else { // The browser URL and router state was not updated before the navigation cancelled so // there's no restoration needed. } } else if (this.canceledNavigationResolution === 'replace') { // TODO(atscott): It seems like we should _always_ reset the state here. It would be a no-op // for `deferred` navigations that haven't change the internal state yet because guards // reject. For 'eager' navigations, it seems like we also really should reset the state // because the navigation was cancelled. Investigate if this can be done by running TGP. if (restoringFromCaughtError) { this.resetState(transition); } this.resetUrlToCurrentUrlTree(); } } resetState(t) { this.routerState = t.currentRouterState; this.currentUrlTree = t.currentUrlTree; // Note here that we use the urlHandlingStrategy to get the reset `rawUrlTree` because it may be // configured to handle only part of the navigation URL. This means we would only want to reset // the part of the navigation handled by the Angular router rather than the whole URL. In // addition, the URLHandlingStrategy may be configured to specifically preserve parts of the URL // when merging, such as the query params so they are not lost on a refresh. this.rawUrlTree = this.urlHandlingStrategy.merge(this.currentUrlTree, t.rawUrl); } resetUrlToCurrentUrlTree() { this.location.replaceState(this.urlSerializer.serialize(this.rawUrlTree), '', this.generateNgRouterState(this.lastSuccessfulId, this.currentPageId)); } generateNgRouterState(navigationId, routerPageId) { if (this.canceledNavigationResolution === 'computed') { return { navigationId, ɵrouterPageId: routerPageId }; } return { navigationId }; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.6", ngImport: i0, type: Router, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.6", ngImport: i0, type: Router, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.6", ngImport: i0, type: Router, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: function () { return []; } }); function validateCommands(commands) { for (let i = 0; i < commands.length; i++) { const cmd = commands[i]; if (cmd == null) { throw new RuntimeError(4008 /* RuntimeErrorCode.NULLISH_COMMAND */, (typeof ngDevMode === 'undefined' || ngDevMode) && `The requested path contains ${cmd} segment at index ${i}`); } } } function isPublicRouterEvent(e) { return (!(e instanceof BeforeActivateRoutes) && !(e instanceof RedirectRequest)); } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vcGFja2FnZXMvcm91dGVyL3NyYy9yb3V0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7OztHQU1HO0FBRUgsT0FBTyxFQUFDLFFBQVEsRUFBQyxNQUFNLGlCQUFpQixDQUFDO0FBQ3pDLE9BQU8sRUFBQyxNQUFNLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFBUSxRQUFRLElBQUksT0FBTyxFQUFFLDBCQUEwQixJQUFJLHlCQUF5QixFQUFFLGFBQWEsSUFBSSxZQUFZLEVBQUMsTUFBTSxlQUFlLENBQUM7QUFDNUssT0FBTyxFQUFhLE9BQU8sRUFBRSxZQUFZLEVBQW1CLE1BQU0sTUFBTSxDQUFDO0FBRXpFLE9BQU8sRUFBQywyQkFBMkIsRUFBRSw2QkFBNkIsRUFBQyxNQUFNLG1CQUFtQixDQUFDO0FBQzdGLE9BQU8sRUFBQyxZQUFZLEVBQUMsTUFBTSw0QkFBNEIsQ0FBQztBQUV4RCxPQUFPLEVBQUMsb0JBQW9CLEVBQVMscUJBQXFCLEVBQUUsZ0JBQWdCLEVBQThCLGFBQWEsRUFBRSxlQUFlLEVBQUUsaUJBQWlCLEVBQUUsZUFBZSxFQUEwQyxlQUFlLEVBQUUsZ0JBQWdCLEVBQUMsTUFBTSxVQUFVLENBQUM7QUFFelEsT0FBTyxFQUFDLDRCQUE0QixFQUFzRCxxQkFBcUIsRUFBb0MsTUFBTSx5QkFBeUIsQ0FBQztBQUNuTCxPQUFPLEVBQUMsYUFBYSxFQUFDLE1BQU0sdUJBQXVCLENBQUM7QUFDcEQsT0FBTyxFQUFDLGtCQUFrQixFQUFDLE1BQU0sd0JBQXdCLENBQUM7QUFDMUQsT0FBTyxFQUFDLG9CQUFvQixFQUFDLE1BQU0saUJBQWlCLENBQUM7QUFDckQsT0FBTyxFQUFDLE1BQU0sRUFBQyxNQUFNLHdCQUF3QixDQUFDO0FBQzlDLE9BQU8sRUFBQyxnQkFBZ0IsRUFBYyxNQUFNLGdCQUFnQixDQUFDO0FBRTdELE9BQU8sRUFBQyxtQkFB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