@angular/router
Version:
Angular - the routing library
1 lines • 102 kB
Source Map (JSON)
{"version":3,"file":"router_module-DTmwsUYo.mjs","sources":["../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/packages/router/src/directives/router_link.ts","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/packages/router/src/directives/router_link_active.ts","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/packages/router/src/router_preloader.ts","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/packages/router/src/router_scroller.ts","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/packages/router/src/provide_router.ts","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/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 Directive,\n ElementRef,\n HostAttributeToken,\n HostBinding,\n HostListener,\n inject,\n Input,\n OnChanges,\n OnDestroy,\n Renderer2,\n ɵRuntimeError as RuntimeError,\n signal,\n SimpleChanges,\n untracked,\n ɵINTERNAL_APPLICATION_ERROR_HANDLER,\n} from '@angular/core';\nimport {Subject, Subscription} from 'rxjs';\nimport {RuntimeErrorCode} from '../errors';\nimport {Event, 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 {isUrlTree, UrlTree} from '../url_tree';\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 components\n * 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 dynamic segments.\n * 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 URL.\n * For instance, suppose the current URL is `/user/(box//aux:team)`.\n * The 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 the root of the\n * app.\n * * If the first segment begins with `./`, or doesn't begin with a slash, the router\n * looks in the children of the current activated route.\n * * If the first segment begins with `../`, the router goes up one level in the 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 URL:\n *\n * ```html\n * <a [routerLink]=\"['/user/bob']\" [queryParams]=\"{debug: true}\" fragment=\"education\">\n * link to user component\n * </a>\n * ```\n * By default, the directive constructs the new URL using the given query parameters.\n * 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}\" queryParamsHandling=\"merge\">\n * link to user component\n * </a>\n * ```\n *\n * `queryParams`, `fragment`, `queryParamsHandling`, `preserveFragment`, and `relativeTo`\n * 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` 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#getCurrentNavigation} to retrieve a saved\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.getCurrentNavigation();\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 custom\n * element must implement the `href` attribute and must list `href` in the array of\n * 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 },\n})\nexport class RouterLink implements OnChanges, OnDestroy {\n /** @nodoc */\n protected readonly reactiveHref = signal<string | null>(null);\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 element.\n * 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 @HostBinding('attr.target') @Input() target?: string;\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() queryParams?: Params | null;\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() fragment?: string;\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() queryParamsHandling?: QueryParamsHandling | null;\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() state?: {[k: string]: any};\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() info?: unknown;\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() relativeTo?: ActivatedRoute | null;\n\n /** Whether a host element is an `<a>`/`<area>` tag or a compatible custom element. */\n private isAnchorElement: boolean;\n\n private subscription?: Subscription;\n\n /** @internal */\n onChanges = new Subject<RouterLink>();\n\n private readonly applicationErrorHandler = inject(ɵINTERNAL_APPLICATION_ERROR_HANDLER);\n private readonly options = inject(ROUTER_CONFIGURATION, {optional: true});\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 // Set the initial href value to whatever exists on the host element already\n this.reactiveHref.set(inject(new HostAttributeToken('href'), {optional: true}));\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 be defined.\n (\n typeof customElements === 'object' &&\n // observedAttributes is an optional static property/getter on a custom element.\n // The spec states that this must be an array of strings.\n (\n customElements.get(tagName) as {observedAttributes?: string[]} | undefined\n )?.observedAttributes?.includes?.('href')\n )\n );\n\n if (!this.isAnchorElement) {\n this.subscribeToNavigationEventsIfNecessary();\n } else {\n this.setTabIndexIfNotOnNativeEl('0');\n }\n }\n\n private subscribeToNavigationEventsIfNecessary() {\n if (this.subscription !== undefined || !this.isAnchorElement) {\n return;\n }\n\n // preserving fragment in router state\n let createSubcription = this.preserveFragment;\n // preserving or merging with query params in router state\n const dependsOnRouterState = (handling?: QueryParamsHandling | null) =>\n handling === 'merge' || handling === 'preserve';\n createSubcription ||= dependsOnRouterState(this.queryParamsHandling);\n createSubcription ||=\n !this.queryParamsHandling && !dependsOnRouterState(this.options?.defaultQueryParamsHandling);\n if (!createSubcription) {\n return;\n }\n\n this.subscription = this.router.events.subscribe((s: Event) => {\n if (s instanceof NavigationEnd) {\n this.updateHref();\n }\n });\n }\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}) preserveFragment: 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}) skipLocationChange: 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}) replaceUrl: boolean = false;\n\n /**\n * Modifies the tab index if there was not a tabindex attribute on the element during\n * 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 change.\n ngOnChanges(changes?: SimpleChanges): void {\n if (\n ngDevMode &&\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 if (this.isAnchorElement) {\n this.updateHref();\n this.subscribeToNavigationEventsIfNecessary();\n }\n // This is subscribed to by `RouterLinkActive` so that it knows to update when there are changes\n // to the RouterLinks it's tracking.\n this.onChanges.next(this);\n }\n\n private routerLinkInput: 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. `['/route']`\n * - **UrlTree**: a `UrlTree` for this link rather than creating one from the commands\n * and other inputs that correspond to properties of `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 = null;\n this.setTabIndexIfNotOnNativeEl(null);\n } else {\n if (isUrlTree(commandsOrUrlTree)) {\n this.routerLinkInput = commandsOrUrlTree;\n } else {\n this.routerLinkInput = Array.isArray(commandsOrUrlTree)\n ? commandsOrUrlTree\n : [commandsOrUrlTree];\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 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 this.subscription?.unsubscribe();\n }\n\n private updateHref(): void {\n const urlTree = this.urlTree;\n this.reactiveHref.set(\n urlTree !== null && this.locationStrategy\n ? (this.locationStrategy?.prepareExternalUrl(this.router.serializeUrl(urlTree)) ?? '')\n : null,\n );\n }\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 get urlTree(): UrlTree | null {\n if (this.routerLinkInput === null) {\n return null;\n } else if (isUrlTree(this.routerLinkInput)) {\n return this.routerLinkInput;\n }\n return this.router.createUrlTree(this.routerLinkInput, {\n // If the `relativeTo` input is not defined, we want to use `this.route` 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}\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 Input,\n OnChanges,\n OnDestroy,\n Optional,\n Output,\n QueryList,\n Renderer2,\n SimpleChanges,\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 {IsActiveMatchOptions} 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 * @ngModule RouterModule\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 `Router.isActive()` function.\n *\n * @see {@link Router#isActive}\n */\n @Input() routerLinkActiveOptions: {exact: boolean} | IsActiveMatchOptions = {exact: false};\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 constructor(\n private router: Router,\n private element: ElementRef,\n private renderer: Renderer2,\n private readonly cdr: ChangeDetectorRef,\n @Optional() private link?: RouterLink,\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: boolean | 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 return (link: RouterLink) => {\n const urlTree = link.urlTree;\n return urlTree ? router.isActive(urlTree, 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} | IsActiveMatchOptions,\n): options is IsActiveMatchOptions {\n return !!(options as IsActiveMatchOptions).paths;\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 * @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 * @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 * @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 if (this.subscription) {\n this.subscription.unsubscribe();\n }\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 `Route: ${route.path}`,\n );\n }\n\n const injectorForCurrentRoute = route._injector ?? injector;\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 let loadedChildren$: Observable<LoadedRouterConfig | null>;\n if (route.loadChildren && route.canLoad === undefined) {\n loadedChildren$ = 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 // 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(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 {Injectable, InjectionToken, NgZone, OnDestroy} 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\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 /** @docs-private */\n constructor(\n readonly urlSerializer: UrlSerializer,\n private transitions: NavigationTransitions,\n public readonly viewportScroller: ViewportScroller,\n private readonly zone: NgZone,\n private options: {\n scrollPositionRestoration?: 'disabled' | 'enabled' | 'top';\n anchorScrolling?: 'disabled' | 'enabled';\n } = {},\n ) {\n // Default both options to 'disabled'\n options.scrollPositionRestoration ||= 'disabled';\n 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)) return;\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]);\n } else if (this.options.scrollPositionRestoration === 'enabled') {\n this.viewportScroller.scrollToPosition(e.position);\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 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 ),\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 {\n HashLocationStrategy,\n LOCATION_INITIALIZED,\n LocationStrategy,\n ViewportScroller,\n} from '@angular/common';\nimport {\n APP_BOOTSTRAP_LISTENER,\n ApplicationRef,\n ComponentRef,\n ENVIRONMENT_INITIALIZER,\n EnvironmentProviders,\n inject,\n InjectionToken,\n Injector,\n makeEnvironmentProviders,\n NgZone,\n provideAppInitializer,\n Provider,\n runInInjectionContext,\n Type,\n ɵperformanceMarkFeature as performanceMarkFeature,\n} from '@angular/core';\nimport {of, Subject} from 'rxjs';\n\nimport {INPUT_BINDER, RoutedComponentInputBinder} from './directives/router_outlet';\nimport {Event, NavigationError, stringifyEvent} from './events';\nimport {RedirectCommand, Routes} from './models';\nimport {NAVIGATION_ERROR_HANDLER, NavigationTransitions} from './navigation_transition';\nimport {Router} from './router';\nimport {InMemoryScrollingOptions, ROUTER_CONFIGURATION, RouterConfigOptions} from './router_config';\nimport {ROUTES} from './router_config_loader';\nimport {PreloadingStrategy, RouterPreloader} from './router_preloader';\nimport {ROUTER_SCROLLER, RouterScroller} from './router_scroller';\nimport {ActivatedRoute} from './router_state';\nimport {UrlSerializer} from './url_tree';\nimport {afterNextNavigation} from './utils/navigations';\nimport {\n CREATE_VIEW_TRANSITION,\n createViewTransition,\n VIEW_TRANSITION_OPTIONS,\n ViewTransitionsFeatureOptions,\n} from './utils/view_transition';\n\n/**\n * Sets up providers necessary to enable `Router` functionality for the application.\n * Allows to configure a set of routes as well as extra features that should be enabled.\n *\n * @usageNotes\n *\n * Basic example of how you can add a Router to your application:\n * ```ts\n * const appRoutes: Routes = [];\n * bootstrapApplication(AppComponent, {\n * providers: [provideRouter(appRoutes)]\n * });\n * ```\n *\n * You can also enable optional features in the Router by adding functions from the `RouterFeatures`\n * type:\n * ```ts\n * const appRoutes: Routes = [];\n * bootstrapApplication(AppComponent,\n * {\n * providers: [\n * provideRouter(appRoutes,\n * withDebugTracing(),\n * withRouterConfig({paramsInheritanceStrategy: 'always'}))\n * ]\n * }\n * );\n * ```\n *\n * @see {@link RouterFeatures}\n *\n * @publicApi\n * @param routes A set of `Route`s to use for the application routing table.\n * @param features Optional features to configure additional router behaviors.\n * @returns A set of providers to setup a Router.\n */\nexport function provideRouter(routes: Routes, ...features: RouterFeatures[]): EnvironmentProviders {\n return makeEnvironmentProviders([\n {provide: ROUTES, multi: true, useValue: routes},\n typeof ngDevMode === 'undefined' || ngDevMode\n ? {provide: ROUTER_IS_PROVIDED, useValue: true}\n : [],\n {provide: ActivatedRoute, useFactory: rootRoute, deps: [Router]},\n {provide: APP_BOOTSTRAP_LISTENER, multi: true, useFactory: getBootstrapListener},\n features.map((feature) => feature.ɵproviders),\n ]);\n}\n\nexport function rootRoute(router: Router): ActivatedRoute {\n return router.routerState.root;\n}\n\n/**\n * Helper type to represent a Router feature.\n *\n * @publicApi\n */\nexport interface RouterFeature<FeatureKind extends RouterFeatureKind> {\n ɵkind: FeatureKind;\n ɵproviders: Array<Provider | EnvironmentProviders>;\n}\n\n/**\n * Helper function to create an object that represents a Router feature.\n */\nfunction routerFeature<FeatureKind extends RouterFeatureKind>(\n kind: FeatureKind,\n providers: Array<Provider | EnvironmentProviders>,\n): RouterFeature<FeatureKind> {\n return {ɵkind: kind, ɵproviders: providers};\n}\n\n/**\n * An Injection token used to indicate whether `provideRouter` or `RouterModule.forRoot` was ever\n * called.\n */\nexport const ROUTER_IS_PROVIDED = new InjectionToken<boolean>('', {\n providedIn: 'root',\n factory: () => false,\n});\n\nconst routerIsProvidedDevModeCheck = {\n provide: ENVIRONMENT_INITIALIZER,\n multi: true,\n useFactory() {\n return () => {\n if (!inject(ROUTER_IS_PROVIDED)) {\n console.warn(\n '`provideRoutes` was called without `provideRouter` or `RouterModule.forRoot`. ' +\n 'This is likely a mistake.',\n );\n }\n };\n },\n};\n\n/**\n * Registers a DI provider for a set of routes.\n * @param routes The route configuration to provide.\n *\n * @usageNotes\n *\n * ```ts\n * @NgModule({\n * providers: [provideRoutes(ROUTES)]\n * })\n * class LazyLoadedChildModule {}\n * ```\n *\n * @deprecated If necessary, provide routes using the `ROUTES` `InjectionToken`.\n * @see {@link ROUTES}\n * @publicApi\n */\nexport function provideRoutes(routes: Routes): Provider[] {\n return [\n {provide: ROUTES, multi: true, useValue: routes},\n typeof ngDevMode === 'undefined' || ngDevMode ? routerIsProvidedDevModeCheck : [],\n ];\n}\n\n/**\n * A type alias for providers returned by `withInMemoryScrolling` for use with `provideRouter`.\n *\n * @see {@link withInMemoryScrolling}\n * @see {@link provideRouter}\n *\n * @publicApi\n */\nexport type InMemoryScrollingFeature = RouterFeature<RouterFeatureKind.InMemoryScrollingFeature>;\n\n/**\n * Enables customizable scrolling behavior for router navigations.\n *\n * @usageNotes\n *\n * Basic example of how you can enable scrolling feature:\n * ```ts\n * const appRoutes: Routes = [];\n * bootstrapApplication(AppComponent,\n * {\n * providers: [\n * provideRouter(appRoutes, withInMemoryScrolling())\n * ]\n * }\n * );\n * ```\n *\n * @see {@link provideRouter}\n * @see {@link ViewportScroller}\n *\n * @publicApi\n * @param options Set of configuration parameters to customize scrolling behavior, see\n * `InMemoryScrollingOptions` for additional information.\n * @returns A set of providers for use with `provideRouter`.\n */\nexport function withInMemoryScrolling(\n options: InMemoryScrollingOptions = {},\n): InMemoryScrollingFeature {\n const providers = [\n {\n provide: ROUTER_SCROLLER,\n useFactory: () => {\n const viewportScroller = inject(ViewportScroller);\n const zone = inject(NgZone);\n const transitions = inject(NavigationTransitions);\n const urlSerializer = inject(UrlSerializer);\n return new RouterScroller(urlSerializer, transitions, viewportScroller, zone, options);\n },\n },\n ];\n return routerFeature(RouterFeatureKind.InMemoryScrollingFeature, providers);\n}\n\nexport function getBootstrapListener() {\n const injector = inject(Injector);\n return (bootstrappedComponentRef: ComponentRef<unknown>) => {\n const ref = injector.get(ApplicationRef);\n\n if (bootstrappedComponentRef !== ref.components[0]) {\n return;\n }\n\n const router = injector.get(Router);\n const bootstrapDone = injector.get(BOOTSTRAP_DONE);\n\n if (injector.get(INITIAL_NAVIGATION) === InitialNavigation.EnabledNonBlocking) {\n router.initialNavigation();\n }\n\n injector.get(ROUTER_PRELOADER, null, {optional: true})?.setUpPreloading();\n injector.get(ROUTER_SCROLLER, null, {optional: true})?.init();\n router.resetRootComponentType(ref.componentTypes[0]);\n if (!bootstrapDone.closed) {\n bootstrapDone.next();\n bootstrapDone.complete();\n bootstrapDone.unsubscribe();\n }\n };\n}\n\n/**\n * A subject used to indicate that the bootstrapping phase is done. When initial navigation is\n * `enabledBlocking`, the first navigation waits until bootstrapping is finished before continuing\n * to the activation phase.\n */\nconst BOOTSTRAP_DONE = new InjectionToken<Subject<void>>(\n typeof ngDevMode === 'undefined' || ngDevMode ? 'bootstrap done indicator' : '',\n {\n factory: () => {\n return new Subject<void>();\n },\n },\n);\n\n/**\n * This and the INITIAL_NAVIGATION token are used internally only. The public API side of this is\n * configured through the `ExtraOptions`.\n *\n * When set to `EnabledBlocking`, the initial navigation starts before the root\n * component is created. The bootstrap is blocked until the initial navigation is complete. This\n * value should be set in case you use [server-side rendering](guide/ssr), but do not enable\n * [hydration](guide/hydration) for your application.\n *\n * When set to `EnabledNonBlocking`, the initial navigation starts after the root component has been\n * created. The bootstrap is not blocked on the completion of the initial navigation.\n *\n * When set to `Disabled`, the initial navigation is not performed. The location listener is set up\n * before the root component gets created. Use if there is a reason to have more control over when\n * the router starts its initial navigation due to some complex initialization logic.\n *\n * @see {@link ExtraOptions}\n */\nconst enum InitialNavigation {\n EnabledBlocking,\n EnabledNonBlocking,\n Disabled,\n}\n\nconst INITIAL_NAVIGATION = new InjectionToken<InitialNavigation>(\n typeof ngDevMode === 'undefined' || ngDevMode ? 'initial navigation' : '',\n {providedIn: 'root', factory: () => InitialNavigation.EnabledNonBlocking},\n);\n\n/**\n * A type alias for providers returned by `withEnabledBlockingInitialNavigation` for use with\n * `provideRouter`.\n *\n * @see {@link withEnabledBlockingInitialNavigation}\n * @see {@link provideRouter}\n *\n * @publicApi\n */\nexport type EnabledBlockingInitialNavigationFeature =\n RouterFeature<RouterFeatureKind.EnabledBlockingInitialNavigationFeature>;\n\n/**\n * A type alias for providers returned by `withEnabledBlockingInitialNavigation` or\n * `withDisabledInitialNavigation` functions for use with `provideRouter`.\n *\n * @see {@link withEnabledBlockingInitialNavigation}\n * @see {@link withDisabledInitialNavigation}\n * @see {@link provideRouter}\n *\n * @publicApi\n */\nexport type InitialNavigationFeature =\n | EnabledBlockingInitialNavigationFeature\n | DisabledInitialNavigationFeature;\n\n/**\n * Configures initial navigation to start before the root component is created.\n *\n * The bootstrap is blocked until the initial navigation is complete. This should be set in case\n * you use [server-side rendering](guide/ssr), but do not enable [hydration](guide/hydration) for\n * your application.\n *\n * @usageNotes\n *\n * Basic example of how you can enable this navigation behavior:\n * ```ts\n * const appRoutes: Routes = [];\n * bootstrapApplication(AppComponent,\n * {\n * providers: [\n * provideRouter(appRoutes, withEnabledBlockingInitialNavigation())\n * ]\n * }\n * );\n * ```\n *\n * @see {@link provideRouter}\n *\n * @publicApi\n * @returns A set of providers for use with `provideRouter`.\n */\nexport function withEnabledBlockingInitialNavigation(): EnabledBlockingInitialNavigationFeature {\n const providers = [\n {provide: INITIAL_NAVIGATION, useValue: InitialNavigation.EnabledBlocking},\n provideAppInitializer(() => {\n const injector = inject(Injector);\n const locationInitialized: Promise<any> = injector.get(\n LOCATION_INITIALIZED,\n Promise.resolve(),\n );\n\n return locationInitialized.then(() => {\n return new Promise((resolve) => {\n const router = injector.get(Router);\n const bootstrapDone = injector.get(BOOTSTRAP_DONE);\n afterNextNavigation(router, () => {\n // Unblock APP_INITIALIZER in case the initial navigation was canceled or errored\n // without a redirect.\n resolve(true);\n });\n\n injector.get(NavigationTransitions).afterPreactivation = () => {\n // Unblock APP_INITIALIZER once we get to `afterPreactivation`. At this point, we\n // assume activation will complete successfully (even though this is not\n // guaranteed).\n resolve(true);\n return bootstrapDone.closed ? of(void 0) : bootstrapDone;\n };\n router.initialNavigation();\n });\n });\n }),\n ];\n return routerFeature(RouterFeatureKind.EnabledBlockingInitialNavigationFeature, providers);\n}\n\n/**\n * A type alias for providers returned by `withDisabledInitialNavigation` for use with\n * `provideRouter`.\n *\n * @see {@link withDisabledInitialNavigation}\n * @see {@link provideRouter}\n *\n * @publicApi\n */\nexport type DisabledInitialNavigationFeature =\n RouterFeature<RouterFeatureKind.DisabledInitialNavigationFeature>