UNPKG

@angular/router

Version:
1 lines 166 kB
{"version":3,"file":"_router_module-chunk.mjs","sources":["../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/router/src/directives/router_link.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/router/src/directives/router_link_active.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/router/src/router_preloader.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/router/src/router_scroller.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/router/src/router_devtools.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/router/src/statemanager/navigation_state_manager.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/router/src/provide_router.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/router/src/router_module.ts"],"sourcesContent":["/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {LocationStrategy} from '@angular/common';\nimport {\n Attribute,\n booleanAttribute,\n computed,\n Directive,\n effect,\n ElementRef,\n HostAttributeToken,\n HostListener,\n inject,\n Injectable,\n Input,\n linkedSignal,\n OnChanges,\n OnDestroy,\n Renderer2,\n signal,\n SimpleChanges,\n untracked,\n ɵINTERNAL_APPLICATION_ERROR_HANDLER,\n ɵRuntimeError as RuntimeError,\n} from '@angular/core';\nimport {Subject} from 'rxjs';\n\nimport {RuntimeErrorCode} from '../errors';\nimport {NavigationEnd} from '../events';\nimport {QueryParamsHandling} from '../models';\nimport {Router} from '../router';\nimport {ROUTER_CONFIGURATION} from '../router_config';\nimport {ActivatedRoute} from '../router_state';\nimport {Params} from '../shared';\nimport {StateManager} from '../statemanager/state_manager';\nimport {isUrlTree, UrlSerializer, UrlTree} from '../url_tree';\n\n// Converts non-reactive router state to reactive state via the NavigationEnd\n// event. This isn't the ideal way of doing things, but is necessary to avoid\n// breaking tests which have mocked the Router.\n@Injectable({providedIn: 'root'})\nexport class ReactiveRouterState {\n private readonly router = inject(Router);\n private readonly stateManager = inject(StateManager);\n readonly fragment = signal<string | null>('');\n readonly queryParams = signal<Params>({});\n readonly path = signal<string>('');\n private readonly serializer = inject(UrlSerializer);\n\n constructor() {\n this.updateState();\n this.router.events?.subscribe((e) => {\n if (e instanceof NavigationEnd) {\n this.updateState();\n }\n });\n }\n\n private updateState() {\n const {fragment, root, queryParams} = this.stateManager.getCurrentUrlTree();\n this.fragment.set(fragment);\n this.queryParams.set(queryParams);\n this.path.set(this.serializer.serialize(new UrlTree(root)));\n }\n}\n\n/**\n * @description\n *\n * When applied to an element in a template, makes that element a link\n * that initiates navigation to a route. Navigation opens one or more routed\n * components in one or more `<router-outlet>` locations on the page.\n *\n * Given a route configuration `[{ path: 'user/:name', component: UserCmp }]`,\n * the following creates a static link to the route:\n * `<a routerLink=\"/user/bob\">link to user component</a>`\n *\n * You can use dynamic values to generate the link.\n * For a dynamic link, pass an array of path segments,\n * followed by the params for each segment.\n * For example, `['/team', teamId, 'user', userName, {details: true}]`\n * generates a link to `/team/11/user/bob;details=true`.\n *\n * Multiple static segments can be merged into one term and combined with\n * dynamic segments. For example, `['/team/11/user', userName, {details: true}]`\n *\n * The input that you provide to the link is treated as a delta to the current\n * URL. For instance, suppose the current URL is `/user/(box//aux:team)`. The\n * link `<a [routerLink]=\"['/user/jim']\">Jim</a>` creates the URL\n * `/user/(jim//aux:team)`.\n * See {@link Router#createUrlTree} for more information.\n *\n * @usageNotes\n *\n * You can use absolute or relative paths in a link, set query parameters,\n * control how parameters are handled, and keep a history of navigation states.\n *\n * ### Relative link paths\n *\n * The first segment name can be prepended with `/`, `./`, or `../`.\n * * If the first segment begins with `/`, the router looks up the route from\n * the root of the app.\n * * If the first segment begins with `./`, or doesn't begin with a slash, the\n * router looks in the children of the current activated route.\n * * If the first segment begins with `../`, the router goes up one level in the\n * route tree.\n *\n * ### Setting and handling query params and fragments\n *\n * The following link adds a query parameter and a fragment to the generated\n * URL:\n *\n * ```html\n * <a [routerLink]=\"['/user/bob']\" [queryParams]=\"{debug: true}\"\n * fragment=\"education\"> link to user component\n * </a>\n * ```\n * By default, the directive constructs the new URL using the given query\n * parameters. The example generates the link: `/user/bob?debug=true#education`.\n *\n * You can instruct the directive to handle query parameters differently\n * by specifying the `queryParamsHandling` option in the link.\n * Allowed values are:\n *\n * - `'merge'`: Merge the given `queryParams` into the current query params.\n * - `'preserve'`: Preserve the current query params.\n *\n * For example:\n *\n * ```html\n * <a [routerLink]=\"['/user/bob']\" [queryParams]=\"{debug: true}\"\n * queryParamsHandling=\"merge\"> link to user component\n * </a>\n * ```\n *\n * `queryParams`, `fragment`, `queryParamsHandling`, `preserveFragment`, and\n * `relativeTo` cannot be used when the `routerLink` input is a `UrlTree`.\n *\n * See {@link UrlCreationOptions#queryParamsHandling}.\n *\n * ### Preserving navigation history\n *\n * You can provide a `state` value to be persisted to the browser's\n * [`History.state`\n * property](https://developer.mozilla.org/en-US/docs/Web/API/History#Properties).\n * For example:\n *\n * ```html\n * <a [routerLink]=\"['/user/bob']\" [state]=\"{tracingId: 123}\">\n * link to user component\n * </a>\n * ```\n *\n * Use {@link Router#currentNavigation} to retrieve a saved Signal\n * navigation-state value. For example, to capture the `tracingId` during the `NavigationStart`\n * event:\n *\n * ```ts\n * // Get NavigationStart events\n * router.events.pipe(filter(e => e instanceof NavigationStart)).subscribe(e => {\n * const navigation = router.currentNavigation();\n * tracingService.trace({id: navigation.extras.state.tracingId});\n * });\n * ```\n *\n * ### RouterLink compatible custom elements\n *\n * In order to make a custom element work with routerLink, the corresponding\n * custom element must implement the `href` attribute and must list `href` in\n * the array of the static property/getter `observedAttributes`.\n *\n * @ngModule RouterModule\n *\n * @publicApi\n */\n@Directive({\n selector: '[routerLink]',\n host: {\n '[attr.href]': 'reactiveHref()',\n '[attr.target]': '_target()',\n },\n})\nexport class RouterLink implements OnChanges, OnDestroy {\n private hrefAttributeValue = inject(new HostAttributeToken('href'), {optional: true});\n /** @docs-private */\n protected readonly reactiveHref = linkedSignal(() => {\n // Never change href for non-anchor elements\n if (!this.isAnchorElement) {\n return this.hrefAttributeValue;\n }\n return this.computeHref(this._urlTree());\n });\n /**\n * Represents an `href` attribute value applied to a host element,\n * when a host element is an `<a>`/`<area>` tag or a compatible custom\n * element. For other tags, the value is `null`.\n */\n get href() {\n return untracked(this.reactiveHref);\n }\n /** @deprecated */\n set href(value: string | null) {\n this.reactiveHref.set(value);\n }\n\n /**\n * Represents the `target` attribute on a host element.\n * This is only used when the host element is\n * an `<a>`/`<area>` tag or a compatible custom element.\n */\n @Input() set target(value: string | undefined) {\n this._target.set(value);\n }\n get target(): string | undefined {\n return untracked(this._target);\n }\n\n /**\n * @docs-private\n * @internal\n */\n protected _target = signal<string | undefined>(undefined);\n\n /**\n * Passed to {@link Router#createUrlTree} as part of the\n * `UrlCreationOptions`.\n * @see {@link UrlCreationOptions#queryParams}\n * @see {@link Router#createUrlTree}\n */\n @Input() set queryParams(value: Params | null | undefined) {\n this._queryParams.set(value);\n }\n get queryParams(): Params | null | undefined {\n return untracked(this._queryParams);\n }\n // Rather than trying deep equality checks or serialization, just allow urlTree to recompute\n // whenever queryParams change (which will be rare).\n private _queryParams = signal<Params | null | undefined>(undefined, {equal: () => false});\n /**\n * Passed to {@link Router#createUrlTree} as part of the\n * `UrlCreationOptions`.\n * @see {@link UrlCreationOptions#fragment}\n * @see {@link Router#createUrlTree}\n */\n @Input() set fragment(value: string | undefined) {\n this._fragment.set(value);\n }\n get fragment(): string | undefined {\n return untracked(this._fragment);\n }\n private _fragment = signal<string | undefined>(undefined);\n /**\n * Passed to {@link Router#createUrlTree} as part of the\n * `UrlCreationOptions`.\n * @see {@link UrlCreationOptions#queryParamsHandling}\n * @see {@link Router#createUrlTree}\n */\n @Input() set queryParamsHandling(value: QueryParamsHandling | null | undefined) {\n this._queryParamsHandling.set(value);\n }\n get queryParamsHandling(): QueryParamsHandling | null | undefined {\n return untracked(this._queryParamsHandling);\n }\n private _queryParamsHandling = signal<QueryParamsHandling | null | undefined>(undefined);\n /**\n * Passed to {@link Router#navigateByUrl} as part of the\n * `NavigationBehaviorOptions`.\n * @see {@link NavigationBehaviorOptions#state}\n * @see {@link Router#navigateByUrl}\n */\n @Input() set state(value: {[k: string]: any} | undefined) {\n this._state.set(value);\n }\n get state(): {[k: string]: any} | undefined {\n return untracked(this._state);\n }\n private _state = signal<{[k: string]: any} | undefined>(undefined, {equal: () => false});\n /**\n * Passed to {@link Router#navigateByUrl} as part of the\n * `NavigationBehaviorOptions`.\n * @see {@link NavigationBehaviorOptions#info}\n * @see {@link Router#navigateByUrl}\n */\n @Input() set info(value: unknown) {\n this._info.set(value);\n }\n get info(): unknown {\n return untracked(this._info);\n }\n private _info = signal<unknown>(undefined, {equal: () => false});\n /**\n * Passed to {@link Router#createUrlTree} as part of the\n * `UrlCreationOptions`.\n * Specify a value here when you do not want to use the default value\n * for `routerLink`, which is the current activated route.\n * Note that a value of `undefined` here will use the `routerLink` default.\n * @see {@link UrlCreationOptions#relativeTo}\n * @see {@link Router#createUrlTree}\n */\n @Input() set relativeTo(value: ActivatedRoute | null | undefined) {\n this._relativeTo.set(value);\n }\n get relativeTo(): ActivatedRoute | null | undefined {\n return untracked(this._relativeTo);\n }\n private _relativeTo = signal<ActivatedRoute | null | undefined>(undefined);\n\n /**\n * Passed to {@link Router#createUrlTree} as part of the\n * `UrlCreationOptions`.\n * @see {@link UrlCreationOptions#preserveFragment}\n * @see {@link Router#createUrlTree}\n */\n @Input({transform: booleanAttribute}) set preserveFragment(value: boolean) {\n this._preserveFragment.set(value);\n }\n get preserveFragment(): boolean {\n return untracked(this._preserveFragment);\n }\n private _preserveFragment = signal<boolean>(false);\n\n /**\n * Passed to {@link Router#navigateByUrl} as part of the\n * `NavigationBehaviorOptions`.\n * @see {@link NavigationBehaviorOptions#skipLocationChange}\n * @see {@link Router#navigateByUrl}\n */\n @Input({transform: booleanAttribute}) set skipLocationChange(value: boolean) {\n this._skipLocationChange.set(value);\n }\n get skipLocationChange(): boolean {\n return untracked(this._skipLocationChange);\n }\n private _skipLocationChange = signal<boolean>(false);\n\n /**\n * Passed to {@link Router#navigateByUrl} as part of the\n * `NavigationBehaviorOptions`.\n * @see {@link NavigationBehaviorOptions#replaceUrl}\n * @see {@link Router#navigateByUrl}\n */\n @Input({transform: booleanAttribute}) set replaceUrl(value: boolean) {\n this._replaceUrl.set(value);\n }\n get replaceUrl(): boolean {\n return untracked(this._replaceUrl);\n }\n private _replaceUrl = signal<boolean>(false);\n\n /**\n * Whether a host element is an `<a>`/`<area>` tag or a compatible custom\n * element.\n */\n private readonly isAnchorElement: boolean;\n /** @internal */\n onChanges = new Subject<RouterLink>();\n private readonly applicationErrorHandler = inject(ɵINTERNAL_APPLICATION_ERROR_HANDLER);\n private readonly options = inject(ROUTER_CONFIGURATION, {optional: true});\n private readonly reactiveRouterState = inject(ReactiveRouterState);\n\n constructor(\n private router: Router,\n private route: ActivatedRoute,\n @Attribute('tabindex') private readonly tabIndexAttribute: string | null | undefined,\n private readonly renderer: Renderer2,\n private readonly el: ElementRef,\n private locationStrategy?: LocationStrategy,\n ) {\n const tagName = el.nativeElement.tagName?.toLowerCase();\n this.isAnchorElement =\n tagName === 'a' ||\n tagName === 'area' ||\n !!(\n // Avoid breaking in an SSR context where customElements might not\n // be defined.\n (\n typeof customElements === 'object' &&\n // observedAttributes is an optional static property/getter on a\n // custom element. The spec states that this must be an array of\n // strings.\n (\n customElements.get(tagName) as {observedAttributes?: string[]} | undefined\n )?.observedAttributes?.includes?.('href')\n )\n );\n\n if (typeof ngDevMode !== 'undefined' && ngDevMode) {\n effect(() => {\n if (\n isUrlTree(this.routerLinkInput()) &&\n (this._fragment() !== undefined ||\n this._queryParams() ||\n this._queryParamsHandling() ||\n this._preserveFragment() ||\n this._relativeTo())\n ) {\n throw new RuntimeError(\n RuntimeErrorCode.INVALID_ROUTER_LINK_INPUTS,\n 'Cannot configure queryParams or fragment when using a UrlTree as the routerLink input value.',\n );\n }\n });\n }\n }\n\n /**\n * Modifies the tab index if there was not a tabindex attribute on the element\n * during instantiation.\n */\n private setTabIndexIfNotOnNativeEl(newTabIndex: string | null) {\n if (this.tabIndexAttribute != null /* both `null` and `undefined` */ || this.isAnchorElement) {\n return;\n }\n this.applyAttributeValue('tabindex', newTabIndex);\n }\n\n /** @docs-private */\n // TODO(atscott): Remove changes parameter in major version as a breaking\n // change.\n ngOnChanges(changes?: SimpleChanges): void {\n // This is subscribed to by `RouterLinkActive` so that it knows to update\n // when there are changes to the RouterLinks it's tracking.\n this.onChanges.next(this);\n }\n\n private routerLinkInput = signal<readonly any[] | UrlTree | null>(null);\n\n /**\n * Commands to pass to {@link Router#createUrlTree} or a `UrlTree`.\n * - **array**: commands to pass to {@link Router#createUrlTree}.\n * - **string**: shorthand for array of commands with just the string, i.e.\n * `['/route']`\n * - **UrlTree**: a `UrlTree` for this link rather than creating one from\n * the commands and other inputs that correspond to properties of\n * `UrlCreationOptions`.\n * - **null|undefined**: effectively disables the `routerLink`\n * @see {@link Router#createUrlTree}\n */\n @Input()\n set routerLink(commandsOrUrlTree: readonly any[] | string | UrlTree | null | undefined) {\n if (commandsOrUrlTree == null) {\n this.routerLinkInput.set(null);\n this.setTabIndexIfNotOnNativeEl(null);\n } else {\n if (isUrlTree(commandsOrUrlTree)) {\n this.routerLinkInput.set(commandsOrUrlTree);\n } else {\n this.routerLinkInput.set(\n Array.isArray(commandsOrUrlTree) ? commandsOrUrlTree : [commandsOrUrlTree],\n );\n }\n this.setTabIndexIfNotOnNativeEl('0');\n }\n }\n\n /** @docs-private */\n @HostListener('click', [\n '$event.button',\n '$event.ctrlKey',\n '$event.shiftKey',\n '$event.altKey',\n '$event.metaKey',\n ])\n onClick(\n button: number,\n ctrlKey: boolean,\n shiftKey: boolean,\n altKey: boolean,\n metaKey: boolean,\n ): boolean {\n const urlTree = this._urlTree();\n\n if (urlTree === null) {\n return true;\n }\n\n if (this.isAnchorElement) {\n if (button !== 0 || ctrlKey || shiftKey || altKey || metaKey) {\n return true;\n }\n\n if (typeof this.target === 'string' && this.target != '_self') {\n return true;\n }\n }\n\n const extras = {\n skipLocationChange: this.skipLocationChange,\n replaceUrl: this.replaceUrl,\n state: this.state,\n info: this.info,\n };\n // navigateByUrl is mocked frequently in tests... Reduce breakages when\n // adding `catch`\n this.router.navigateByUrl(urlTree, extras)?.catch((e) => {\n this.applicationErrorHandler(e);\n });\n\n // Return `false` for `<a>` elements to prevent default action\n // and cancel the native behavior, since the navigation is handled\n // by the Router.\n return !this.isAnchorElement;\n }\n\n /** @docs-private */\n ngOnDestroy(): any {}\n\n private applyAttributeValue(attrName: string, attrValue: string | null) {\n const renderer = this.renderer;\n const nativeElement = this.el.nativeElement;\n if (attrValue !== null) {\n renderer.setAttribute(nativeElement, attrName, attrValue);\n } else {\n renderer.removeAttribute(nativeElement, attrName);\n }\n }\n\n /** @internal */\n _urlTree = computed(\n () => {\n // Track path changes. It's knowing which segments we actually depend on is somewhat difficult\n this.reactiveRouterState.path();\n if (this._preserveFragment()) {\n this.reactiveRouterState.fragment();\n }\n const shouldTrackParams = (handling: QueryParamsHandling | undefined | null) =>\n handling === 'preserve' || handling === 'merge';\n if (\n shouldTrackParams(this._queryParamsHandling()) ||\n shouldTrackParams(this.options?.defaultQueryParamsHandling)\n ) {\n this.reactiveRouterState.queryParams();\n }\n\n const routerLinkInput = this.routerLinkInput();\n if (routerLinkInput === null || !this.router.createUrlTree) {\n return null;\n } else if (isUrlTree(routerLinkInput)) {\n return routerLinkInput;\n }\n return this.router.createUrlTree(routerLinkInput, {\n // If the `relativeTo` input is not defined, we want to use `this.route`\n // by default.\n // Otherwise, we should use the value provided by the user in the input.\n relativeTo: this._relativeTo() !== undefined ? this._relativeTo() : this.route,\n queryParams: this._queryParams(),\n fragment: this._fragment(),\n queryParamsHandling: this._queryParamsHandling(),\n preserveFragment: this._preserveFragment(),\n });\n },\n {equal: (a, b) => this.computeHref(a) === this.computeHref(b)},\n );\n\n get urlTree(): UrlTree | null {\n return untracked(this._urlTree);\n }\n\n private computeHref(urlTree: UrlTree | null): string | null {\n return urlTree !== null && this.locationStrategy\n ? (this.locationStrategy?.prepareExternalUrl(this.router.serializeUrl(urlTree)) ?? '')\n : null;\n }\n}\n\n/**\n * @description\n * An alias for the `RouterLink` directive.\n * Deprecated since v15, use `RouterLink` directive instead.\n *\nexport { RouterLink as RouterLinkWithHref };\nnstead.\n * @publicApi\n */\nexport {RouterLink as RouterLinkWithHref};\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {\n AfterContentInit,\n ChangeDetectorRef,\n ContentChildren,\n Directive,\n ElementRef,\n EventEmitter,\n inject,\n Input,\n OnChanges,\n OnDestroy,\n Output,\n QueryList,\n Renderer2,\n SimpleChanges,\n untracked,\n} from '@angular/core';\nimport {from, of, Subscription} from 'rxjs';\nimport {mergeAll} from 'rxjs/operators';\n\nimport {Event, NavigationEnd} from '../events';\nimport {Router} from '../router';\nimport {isActive, IsActiveMatchOptions, exactMatchOptions, subsetMatchOptions} from '../url_tree';\n\nimport {RouterLink} from './router_link';\n\n/**\n *\n * @description\n *\n * Tracks whether the linked route of an element is currently active, and allows you\n * to specify one or more CSS classes to add to the element when the linked route\n * is active.\n *\n * Use this directive to create a visual distinction for elements associated with an active route.\n * For example, the following code highlights the word \"Bob\" when the router\n * activates the associated route:\n *\n * ```html\n * <a routerLink=\"/user/bob\" routerLinkActive=\"active-link\">Bob</a>\n * ```\n *\n * Whenever the URL is either '/user' or '/user/bob', the \"active-link\" class is\n * added to the anchor tag. If the URL changes, the class is removed.\n *\n * You can set more than one class using a space-separated string or an array.\n * For example:\n *\n * ```html\n * <a routerLink=\"/user/bob\" routerLinkActive=\"class1 class2\">Bob</a>\n * <a routerLink=\"/user/bob\" [routerLinkActive]=\"['class1', 'class2']\">Bob</a>\n * ```\n *\n * To add the classes only when the URL matches the link exactly, add the option `exact: true`:\n *\n * ```html\n * <a routerLink=\"/user/bob\" routerLinkActive=\"active-link\" [routerLinkActiveOptions]=\"{exact:\n * true}\">Bob</a>\n * ```\n *\n * To directly check the `isActive` status of the link, assign the `RouterLinkActive`\n * instance to a template variable.\n * For example, the following checks the status without assigning any CSS classes:\n *\n * ```html\n * <a routerLink=\"/user/bob\" routerLinkActive #rla=\"routerLinkActive\">\n * Bob {{ rla.isActive ? '(already open)' : ''}}\n * </a>\n * ```\n *\n * You can apply the `RouterLinkActive` directive to an ancestor of linked elements.\n * For example, the following sets the active-link class on the `<div>` parent tag\n * when the URL is either '/user/jim' or '/user/bob'.\n *\n * ```html\n * <div routerLinkActive=\"active-link\" [routerLinkActiveOptions]=\"{exact: true}\">\n * <a routerLink=\"/user/jim\">Jim</a>\n * <a routerLink=\"/user/bob\">Bob</a>\n * </div>\n * ```\n *\n * The `RouterLinkActive` directive can also be used to set the aria-current attribute\n * to provide an alternative distinction for active elements to visually impaired users.\n *\n * For example, the following code adds the 'active' class to the Home Page link when it is\n * indeed active and in such case also sets its aria-current attribute to 'page':\n *\n * ```html\n * <a routerLink=\"/\" routerLinkActive=\"active\" ariaCurrentWhenActive=\"page\">Home Page</a>\n * ```\n *\n * NOTE: RouterLinkActive is a `ContentChildren` query.\n * Content children queries do not retrieve elements or directives that are in other components' templates, since a component's template is always a black box to its ancestors.\n *\n * @ngModule RouterModule\n *\n * @see [Detect active current route with RouterLinkActive](guide/routing/read-route-state#detect-active-current-route-with-routerlinkactive)\n *\n * @publicApi\n */\n@Directive({\n selector: '[routerLinkActive]',\n exportAs: 'routerLinkActive',\n})\nexport class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit {\n @ContentChildren(RouterLink, {descendants: true}) links!: QueryList<RouterLink>;\n\n private classes: string[] = [];\n private routerEventsSubscription: Subscription;\n private linkInputChangesSubscription?: Subscription;\n private _isActive = false;\n\n get isActive(): boolean {\n return this._isActive;\n }\n\n /**\n * Options to configure how to determine if the router link is active.\n *\n * These options are passed to the `isActive()` function.\n *\n * @see {@link isActive}\n */\n @Input() routerLinkActiveOptions: {exact: boolean} | Partial<IsActiveMatchOptions> = {\n exact: false,\n };\n\n /**\n * Aria-current attribute to apply when the router link is active.\n *\n * Possible values: `'page'` | `'step'` | `'location'` | `'date'` | `'time'` | `true` | `false`.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current}\n */\n @Input() ariaCurrentWhenActive?: 'page' | 'step' | 'location' | 'date' | 'time' | true | false;\n\n /**\n *\n * You can use the output `isActiveChange` to get notified each time the link becomes\n * active or inactive.\n *\n * Emits:\n * true -> Route is active\n * false -> Route is inactive\n *\n * ```html\n * <a\n * routerLink=\"/user/bob\"\n * routerLinkActive=\"active-link\"\n * (isActiveChange)=\"this.onRouterLinkActive($event)\">Bob</a>\n * ```\n */\n @Output() readonly isActiveChange: EventEmitter<boolean> = new EventEmitter();\n\n private link = inject(RouterLink, {optional: true});\n\n constructor(\n private router: Router,\n private element: ElementRef,\n private renderer: Renderer2,\n private readonly cdr: ChangeDetectorRef,\n ) {\n this.routerEventsSubscription = router.events.subscribe((s: Event) => {\n if (s instanceof NavigationEnd) {\n this.update();\n }\n });\n }\n\n /** @docs-private */\n ngAfterContentInit(): void {\n // `of(null)` is used to force subscribe body to execute once immediately (like `startWith`).\n of(this.links.changes, of(null))\n .pipe(mergeAll())\n .subscribe((_) => {\n this.update();\n this.subscribeToEachLinkOnChanges();\n });\n }\n\n private subscribeToEachLinkOnChanges() {\n this.linkInputChangesSubscription?.unsubscribe();\n const allLinkChanges = [...this.links.toArray(), this.link]\n .filter((link): link is RouterLink => !!link)\n .map((link) => link.onChanges);\n this.linkInputChangesSubscription = from(allLinkChanges)\n .pipe(mergeAll())\n .subscribe((link) => {\n if (this._isActive !== this.isLinkActive(this.router)(link)) {\n this.update();\n }\n });\n }\n\n @Input()\n set routerLinkActive(data: string[] | string) {\n const classes = Array.isArray(data) ? data : data.split(' ');\n this.classes = classes.filter((c) => !!c);\n }\n\n /** @docs-private */\n ngOnChanges(changes: SimpleChanges): void {\n this.update();\n }\n /** @docs-private */\n ngOnDestroy(): void {\n this.routerEventsSubscription.unsubscribe();\n this.linkInputChangesSubscription?.unsubscribe();\n }\n\n private update(): void {\n if (!this.links || !this.router.navigated) return;\n\n queueMicrotask(() => {\n const hasActiveLinks = this.hasActiveLinks();\n this.classes.forEach((c) => {\n if (hasActiveLinks) {\n this.renderer.addClass(this.element.nativeElement, c);\n } else {\n this.renderer.removeClass(this.element.nativeElement, c);\n }\n });\n if (hasActiveLinks && this.ariaCurrentWhenActive !== undefined) {\n this.renderer.setAttribute(\n this.element.nativeElement,\n 'aria-current',\n this.ariaCurrentWhenActive.toString(),\n );\n } else {\n this.renderer.removeAttribute(this.element.nativeElement, 'aria-current');\n }\n\n // Only emit change if the active state changed.\n if (this._isActive !== hasActiveLinks) {\n this._isActive = hasActiveLinks;\n this.cdr.markForCheck();\n // Emit on isActiveChange after classes are updated\n this.isActiveChange.emit(hasActiveLinks);\n }\n });\n }\n\n private isLinkActive(router: Router): (link: RouterLink) => boolean {\n const options: Partial<IsActiveMatchOptions> = isActiveMatchOptions(\n this.routerLinkActiveOptions,\n )\n ? this.routerLinkActiveOptions\n : // While the types should disallow `undefined` here, it's possible without strict inputs\n (this.routerLinkActiveOptions.exact ?? false)\n ? {...exactMatchOptions}\n : {...subsetMatchOptions};\n\n return (link: RouterLink) => {\n const urlTree = link.urlTree;\n return urlTree ? untracked(isActive(urlTree, router, options)) : false;\n };\n }\n\n private hasActiveLinks(): boolean {\n const isActiveCheckFn = this.isLinkActive(this.router);\n return (this.link && isActiveCheckFn(this.link)) || this.links.some(isActiveCheckFn);\n }\n}\n\n/**\n * Use instead of `'paths' in options` to be compatible with property renaming\n */\nfunction isActiveMatchOptions(\n options: {exact: boolean} | Partial<IsActiveMatchOptions>,\n): options is Partial<IsActiveMatchOptions> {\n const o = options as Partial<IsActiveMatchOptions>;\n return !!(o.paths || o.matrixParams || o.queryParams || o.fragment);\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {createEnvironmentInjector, EnvironmentInjector, Injectable, OnDestroy} from '@angular/core';\nimport {from, Observable, of, Subscription} from 'rxjs';\nimport {catchError, concatMap, filter, mergeAll, mergeMap} from 'rxjs/operators';\n\nimport {Event, NavigationEnd} from './events';\nimport {LoadedRouterConfig, Route, Routes} from './models';\nimport {Router} from './router';\nimport {RouterConfigLoader} from './router_config_loader';\n\n/**\n * @description\n *\n * Provides a preloading strategy.\n *\n * @see [Preloading strategy](guide/routing/customizing-route-behavior#preloading-strategy)\n * @publicApi\n */\nexport abstract class PreloadingStrategy {\n abstract preload(route: Route, fn: () => Observable<any>): Observable<any>;\n}\n\n/**\n * @description\n *\n * Provides a preloading strategy that preloads all modules as quickly as possible.\n *\n * ```ts\n * RouterModule.forRoot(ROUTES, {preloadingStrategy: PreloadAllModules})\n * ```\n *\n * ```ts\n * export const appConfig: ApplicationConfig = {\n * providers: [\n * provideRouter(\n * routes,\n * withPreloading(PreloadAllModules)\n * )\n * ]\n * };\n * ```\n *\n *\n * @see [Preloading strategy](guide/routing/customizing-route-behavior#preloading-strategy)\n *\n * @publicApi\n */\n@Injectable({providedIn: 'root'})\nexport class PreloadAllModules implements PreloadingStrategy {\n preload(route: Route, fn: () => Observable<any>): Observable<any> {\n return fn().pipe(catchError(() => of(null)));\n }\n}\n\n/**\n * @description\n *\n * Provides a preloading strategy that does not preload any modules.\n *\n * This strategy is enabled by default.\n *\n * @see [Preloading strategy](guide/routing/customizing-route-behavior#preloading-strategy)\n *\n * @publicApi\n */\n@Injectable({providedIn: 'root'})\nexport class NoPreloading implements PreloadingStrategy {\n preload(route: Route, fn: () => Observable<any>): Observable<any> {\n return of(null);\n }\n}\n\n/**\n * The preloader optimistically loads all router configurations to\n * make navigations into lazily-loaded sections of the application faster.\n *\n * The preloader runs in the background. When the router bootstraps, the preloader\n * starts listening to all navigation events. After every such event, the preloader\n * will check if any configurations can be loaded lazily.\n *\n * If a route is protected by `canLoad` guards, the preloaded will not load it.\n *\n * @publicApi\n */\n@Injectable({providedIn: 'root'})\nexport class RouterPreloader implements OnDestroy {\n private subscription?: Subscription;\n\n constructor(\n private router: Router,\n private injector: EnvironmentInjector,\n private preloadingStrategy: PreloadingStrategy,\n private loader: RouterConfigLoader,\n ) {}\n\n setUpPreloading(): void {\n this.subscription = this.router.events\n .pipe(\n filter((e: Event) => e instanceof NavigationEnd),\n concatMap(() => this.preload()),\n )\n .subscribe(() => {});\n }\n\n preload(): Observable<any> {\n return this.processRoutes(this.injector, this.router.config);\n }\n\n /** @docs-private */\n ngOnDestroy(): void {\n this.subscription?.unsubscribe();\n }\n\n private processRoutes(injector: EnvironmentInjector, routes: Routes): Observable<void> {\n const res: Observable<any>[] = [];\n for (const route of routes) {\n if (route.providers && !route._injector) {\n route._injector = createEnvironmentInjector(\n route.providers,\n injector,\n typeof ngDevMode === 'undefined' || ngDevMode ? `Route: ${route.path}` : '',\n );\n }\n\n const injectorForCurrentRoute = route._injector ?? injector;\n if (route._loadedNgModuleFactory && !route._loadedInjector) {\n route._loadedInjector =\n route._loadedNgModuleFactory.create(injectorForCurrentRoute).injector;\n }\n const injectorForChildren = route._loadedInjector ?? injectorForCurrentRoute;\n\n // Note that `canLoad` is only checked as a condition that prevents `loadChildren` and not\n // `loadComponent`. `canLoad` guards only block loading of child routes by design. This\n // happens as a consequence of needing to descend into children for route matching immediately\n // while component loading is deferred until route activation. Because `canLoad` guards can\n // have side effects, we cannot execute them here so we instead skip preloading altogether\n // when present. Lastly, it remains to be decided whether `canLoad` should behave this way\n // at all. Code splitting and lazy loading is separate from client-side authorization checks\n // and should not be used as a security measure to prevent loading of code.\n if (\n (route.loadChildren && !route._loadedRoutes && route.canLoad === undefined) ||\n (route.loadComponent && !route._loadedComponent)\n ) {\n res.push(this.preloadConfig(injectorForCurrentRoute, route));\n }\n if (route.children || route._loadedRoutes) {\n res.push(this.processRoutes(injectorForChildren, (route.children ?? route._loadedRoutes)!));\n }\n }\n return from(res).pipe(mergeAll());\n }\n\n private preloadConfig(injector: EnvironmentInjector, route: Route): Observable<void> {\n return this.preloadingStrategy.preload(route, () => {\n if (injector.destroyed) {\n return of(null);\n }\n let loadedChildren$: Observable<LoadedRouterConfig | null>;\n if (route.loadChildren && route.canLoad === undefined) {\n loadedChildren$ = from(this.loader.loadChildren(injector, route));\n } else {\n loadedChildren$ = of(null);\n }\n\n const recursiveLoadChildren$ = loadedChildren$.pipe(\n mergeMap((config: LoadedRouterConfig | null) => {\n if (config === null) {\n return of(void 0);\n }\n route._loadedRoutes = config.routes;\n route._loadedInjector = config.injector;\n route._loadedNgModuleFactory = config.factory;\n // If the loaded config was a module, use that as the module/module injector going\n // forward. Otherwise, continue using the current module/module injector.\n return this.processRoutes(config.injector ?? injector, config.routes);\n }),\n );\n if (route.loadComponent && !route._loadedComponent) {\n const loadComponent$ = this.loader.loadComponent(injector, route);\n return from([recursiveLoadChildren$, loadComponent$]).pipe(mergeAll());\n } else {\n return recursiveLoadChildren$;\n }\n });\n }\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {ViewportScroller} from '@angular/common';\nimport {inject, Injectable, InjectionToken, NgZone, OnDestroy, untracked} from '@angular/core';\nimport {Unsubscribable} from 'rxjs';\n\nimport {\n IMPERATIVE_NAVIGATION,\n NavigationEnd,\n NavigationSkipped,\n NavigationSkippedCode,\n NavigationStart,\n NavigationTrigger,\n Scroll,\n} from './events';\nimport {NavigationTransitions} from './navigation_transition';\nimport {UrlSerializer} from './url_tree';\n\nexport const ROUTER_SCROLLER = new InjectionToken<RouterScroller>(\n typeof ngDevMode !== 'undefined' && ngDevMode ? 'Router Scroller' : '',\n);\n\n@Injectable()\nexport class RouterScroller implements OnDestroy {\n private routerEventsSubscription?: Unsubscribable;\n private scrollEventsSubscription?: Unsubscribable;\n\n private lastId = 0;\n private lastSource: NavigationTrigger | undefined = IMPERATIVE_NAVIGATION;\n private restoredId = 0;\n private store: {[key: string]: [number, number]} = {};\n\n private readonly urlSerializer = inject(UrlSerializer);\n private readonly zone = inject(NgZone);\n readonly viewportScroller = inject(ViewportScroller);\n private readonly transitions = inject(NavigationTransitions);\n\n /** @docs-private */\n constructor(\n private options: {\n scrollPositionRestoration?: 'disabled' | 'enabled' | 'top';\n anchorScrolling?: 'disabled' | 'enabled';\n },\n ) {\n // Default both options to 'disabled'\n this.options.scrollPositionRestoration ||= 'disabled';\n this.options.anchorScrolling ||= 'disabled';\n }\n\n init(): void {\n // we want to disable the automatic scrolling because having two places\n // responsible for scrolling results race conditions, especially given\n // that browser don't implement this behavior consistently\n if (this.options.scrollPositionRestoration !== 'disabled') {\n this.viewportScroller.setHistoryScrollRestoration('manual');\n }\n this.routerEventsSubscription = this.createScrollEvents();\n this.scrollEventsSubscription = this.consumeScrollEvents();\n }\n\n private createScrollEvents() {\n return this.transitions.events.subscribe((e) => {\n if (e instanceof NavigationStart) {\n // store the scroll position of the current stable navigations.\n this.store[this.lastId] = this.viewportScroller.getScrollPosition();\n this.lastSource = e.navigationTrigger;\n this.restoredId = e.restoredState ? e.restoredState.navigationId : 0;\n } else if (e instanceof NavigationEnd) {\n this.lastId = e.id;\n this.scheduleScrollEvent(e, this.urlSerializer.parse(e.urlAfterRedirects).fragment);\n } else if (\n e instanceof NavigationSkipped &&\n e.code === NavigationSkippedCode.IgnoredSameUrlNavigation\n ) {\n this.lastSource = undefined;\n this.restoredId = 0;\n this.scheduleScrollEvent(e, this.urlSerializer.parse(e.url).fragment);\n }\n });\n }\n\n private consumeScrollEvents() {\n return this.transitions.events.subscribe((e) => {\n if (!(e instanceof Scroll) || e.scrollBehavior === 'manual') return;\n const instantScroll: ScrollOptions = {behavior: 'instant'};\n // a popstate event. The pop state event will always ignore anchor scrolling.\n if (e.position) {\n if (this.options.scrollPositionRestoration === 'top') {\n this.viewportScroller.scrollToPosition([0, 0], instantScroll);\n } else if (this.options.scrollPositionRestoration === 'enabled') {\n this.viewportScroller.scrollToPosition(e.position, instantScroll);\n }\n // imperative navigation \"forward\"\n } else {\n if (e.anchor && this.options.anchorScrolling === 'enabled') {\n this.viewportScroller.scrollToAnchor(e.anchor);\n } else if (this.options.scrollPositionRestoration !== 'disabled') {\n this.viewportScroller.scrollToPosition([0, 0]);\n }\n }\n });\n }\n\n private scheduleScrollEvent(\n routerEvent: NavigationEnd | NavigationSkipped,\n anchor: string | null,\n ): void {\n const scroll = untracked(this.transitions.currentNavigation)?.extras.scroll;\n this.zone.runOutsideAngular(async () => {\n // The scroll event needs to be delayed until after change detection. Otherwise, we may\n // attempt to restore the scroll position before the router outlet has fully rendered the\n // component by executing its update block of the template function.\n //\n // #57109 (we need to wait at least a macrotask before scrolling. AfterNextRender resolves in microtask event loop with Zones)\n // We could consider _also_ waiting for a render promise though one should have already happened or been scheduled by this point\n // and should definitely happen before rAF/setTimeout.\n // #53985 (cannot rely solely on setTimeout because a frame may paint before the timeout)\n await new Promise((resolve) => {\n setTimeout(resolve);\n if (typeof requestAnimationFrame !== 'undefined') {\n requestAnimationFrame(resolve);\n }\n });\n this.zone.run(() => {\n this.transitions.events.next(\n new Scroll(\n routerEvent,\n this.lastSource === 'popstate' ? this.store[this.restoredId] : null,\n anchor,\n scroll,\n ),\n );\n });\n });\n }\n\n /** @docs-private */\n ngOnDestroy(): void {\n this.routerEventsSubscription?.unsubscribe();\n this.scrollEventsSubscription?.unsubscribe();\n }\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {Injector} from '@angular/core';\nimport {Router} from './router';\nimport {Route} from './models';\n\n/**\n * Returns the loaded routes for a given route.\n */\nexport function getLoadedRoutes(route: Route): Route[] | undefined {\n return route._loadedRoutes;\n}\n\n/**\n * Returns the Router instance from the given injector, or null if not available.\n */\nexport function getRouterInstance(injector: Injector): Router | null {\n return injector.get(Router, null, {optional: true});\n}\n\n/**\n * Navigates the given router to the specified URL.\n * Throws if the provided router is not an Angular Router.\n */\nexport function navigateByUrl(router: Router, url: string): Promise<boolean> {\n if (!(router instanceof Router)) {\n throw new Error('The provided router is not an Angular Router.');\n }\n return router.navigateByUrl(url);\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\nimport {\n afterNextRender,\n ɵpromiseWithResolvers as promiseWithResolvers,\n DestroyRef,\n EnvironmentInjector,\n inject,\n Injectable,\n} from '@angular/core';\n\nimport {\n PlatformLocation,\n PlatformNavigation,\n ɵPRECOMMIT_HANDLER_SUPPORTED as PRECOMMIT_HANDLER_SUPPORTED,\n} from '@angular/common';\nimport {StateManager} from './state_manager';\nimport {\n NavigationExtras,\n RestoredState,\n Navigation as RouterNavigation,\n} from '../navigation_transition';\nimport {\n BeforeActivateRoutes,\n BeforeRoutesRecognized,\n isRedirectingEvent,\n NavigationCancel,\n NavigationCancellationCode,\n NavigationEnd,\n NavigationError,\n NavigationSkipped,\n NavigationStart,\n NavigationTrigger,\n PrivateRouterEvents,\n} from '../events';\nimport {Subject, SubscriptionLike} from 'rxjs';\nimport {UrlTree} from '../url_tree';\nimport {ROUTER_SCROLLER} from '../router_scroller';\n\ntype NavigationInfo = {ɵrouterInfo: {intercept: boolean}};\n\n@Injectable({providedIn: 'root'})\n/**\n * A `StateManager` that uses the browser's Navigation API to get the state of a `popstate`\n * event.\n *\n * This class is currently an extension of `HistoryStateManager` and is used when the\n * Navigation API is available. It overrides the behavior of listening to `popstate` events\n * to retrieve the state from `navigation.currentEntry` instead of `history.state` since\n * history and navigation states are separate.\n *\n * This implementation is not complete - it does not integrate at all with navigation API other than\n * providing the right state on popstate. It needs to manage the whole lifecycle of the navigation\n * by intercepting the navigation event.\n */\nexport class NavigationStateManager extends StateManager {\n private readonly injector = inject(EnvironmentInjector);\n private readonly navigation = inject(PlatformNavigation);\n private readonly inMemoryScrollingEnabled = inject(ROUTER_SCROLLER, {optional: true}) !== null;\n /** The base origin of the application, extracted from PlatformLocation. */\n private readonly base = new URL(inject(PlatformLocation).href).origin;\n /** The root URL of the Angular application, considering the base href. */\n private readonly appRootURL = new URL(this.location.prepareExternalUrl?.('/') ?? '/', this.base)\n .href;\n private readonly precommitHandlerSupported = inject(PRECOMMIT_HANDLER_SUPPORTED);\n /**\n * The `NavigationHistoryEntry` from the Navigation API that corresponds to the last successfully\n * activated router state. This is crucial for restoring the browser state if an ongoing navigation\n * is canceled or fails, allowing a precise rollback to a known good entry.\n * It's updated on `navigatesuccess`.\n */\n private activeHistoryEntry: NavigationHistoryEntry = this.navigation.currentEntry!;\n\n /**\n * Holds state related to the currently processing navigation that was intercepted from a\n * `navigate` event. This includes the router's internal `Navigation` object.\n */\n private currentNavigation: {\n removeAbortListener?: () => void;\n /** The Angular Router's internal representation of the ongoing navigation. */\n routerTransition?: RouterNavigation;\n /** Function to reject the intercepted navigation event. */\n rejectNavigateEvent?: (reason?: any) => void;\n /** Function to resolve the intercepted navigation event. */\n resolveHandler?: (v: void) => void;\n navigationEvent?: NavigateEvent;\n commitUrl?: () => Promise<void>;\n } = {};\n\n /**\n * Subject used to notify listeners (typically the `Router`) of URL/state changes\n * that were initiated outside the Angular Router but detected via the Navigation API's\n * `navigate` event (e.g., user clicking browser back/forward, or manual URL changes if\n * interceptable by the Navigation API).\n */\n private nonRouterCurrentEntryChangeSubject = new Subject<{\n path: string;\n state: RestoredState | null | undefined;\n }>();\n\n nonRouterEntryChangeListener?: SubscriptionLike;\n private get registered() {\n return (\n this.nonRouterEntryChangeListener !== undefined && !this.nonRouterEntryChangeListener.closed\n );\n }\n\n constructor() {\n super();\n\n // Listen to the 'navigate' event from the Navigation API.\n // This is the primary entry point for intercepting and handling navigations.\n const navigateListener = (event: NavigateEvent) => {\n this.handleNavigate(event);\n };\n this.navigation.addEventListener('navigate', navigateListener);\n inject(DestroyRef).onDestroy(() =>\n this.navigation.removeEventListener('navigate', navigateListener),\n );\n }\n\n override registerNonRouterCurrentEntryChangeListener(\n listener: (\n url: string,\n state: RestoredState | null | undefined,\n trigger: NavigationTrigger,\n extras: NavigationExtras,\n ) => void,\n ): SubscriptionLike {\n this.activeHistoryEntry = this.navigation.currentEntry!;\n this.nonRouterEntryChangeListener = this.nonRouterCurrentEntryChangeSubject.subscribe(\n ({path, state}) => {\n listener(\n path,\n state,\n 'popstate',\n !this.precommitHandlerSupported ? {replaceUrl: true} : {},\n );\n },\n );\n return this.nonRouterEntryChangeListener;\n }\n\n /**\n * Handles router events emitted by the `NavigationTransitions` service.\n * This method orchestrates the interaction with the Navigation API based on the\n * current stage of the router's internal navigation pipeline.\n *\n * @param e The router event (e.g., `NavigationStart`, `NavigationEnd`).\n * @param transition The Angular Router's internal navigation object.\n */\n override async handleRouterEvent(\n e: Event | PrivateRouterEvents,\n transition: RouterNavigation,\n ): Promise<void> {\n this.current