600 Commits

Author SHA1 Message Date
SkyZeroZx a205de99ba test(router): remove redundant change detection configuration
OnPush is now the default change detection strategy, so the explicit test configuration is no longer needed.

(cherry picked from commit a958d7fe30)
2026-09-10 14:30:35 -07:00
Shuaib Hasan Akib 6da6d7af65 refactor(core): use native Promise.withResolvers() in remaining tests
Replaces the remaining hand-rolled deferred promise implementations
with the native `Promise.withResolvers()` API and removes the now
unused helper and import.

Follow-up to #69739.

(cherry picked from commit 1bc7e3c2c3)
2026-09-09 16:17:09 +02:00
arturovt 95c01e9cde fix(router): keep detached route subtree contexts isolated and intact
When a route is detached for `RouteReuseStrategy`, its component and child
`RouterOutlet`s stay alive and keep referencing the `ChildrenOutletContexts`
they were created with. `detachAndStoreRouteSubtree` used to call
`onOutletDeactivated()`, which swaps that object's Map for an empty one, so
after re-attaching, the inner outlet and the router read from two different
context trees and deeper child routes (e.g. an `edit` route under a reused
list) never rendered.

Now, on detach: take the child contexts map as-is (destruction still calls
`onOutletDeactivated()` to prune) and give the `OutletContext` a fresh
`ChildrenOutletContexts`. This keeps the detached component rendering from
its stored map once re-attached, and prevents whatever activates next in
the same parent outlet (e.g. a sibling tab) from mutating or wiping that
stored map.

Fixes #57285

(cherry picked from commit 8227e5cf6d)
2026-09-09 15:53:34 +02:00
Andrew Scott c84079ebf2 refactor(router): add component input binding support for router resources
Integrates component input binding (via `withComponentInputBinding()`) with router resources.

(cherry picked from commit df83a34088)
2026-08-25 15:21:00 -07:00
Andrew Scott d2e3bac33b refactor(router): add support for blocking router resources
Extends router resource integration to support blocking resources during navigation transitions.

(cherry picked from commit fa2aca969f)
2026-08-21 11:38:23 -07:00
Andrew Scott 84a210e8f3 refactor(router): add support for non-blocking router resources (#70211)
Introduces support for route-level resources via the `resources` property on route definitions, enabled with `withRouterResources()`.

Router resources provide a reactive, signal-based alternative to resolvers, allowing routes to declare resources tied to route parameters and route lifecycle:
- The `resources` function executes during navigation transitions within an injection context scoped to the route (`_localInjector`).
- It receives a `ResourceContext` containing signals for `params`, `queryParams`, `fragment`, and `data`, alongside the static `snapshot`.
- For newly created routes, `resources` runs once and attaches to `ActivatedRoute.resources`. For reused routes, parameter signals update reactively to trigger new data fetches while keeping resource references stable.
- Wrapped resources (`routerResource`) provide transactional stability: snapshots are frozen during active navigations to prevent UI jitter, unfreezing on `NavigationEnd`.
- On cancelled navigations or errors, rollback recovery retains the frozen snapshot until reverted signals settle, avoiding flashes of loading state. Manual reloads are rejected while frozen.
- Local injectors are automatically cleaned up if navigations are cancelled or rolled back.

Note that this commit only implements non-blocking resources (marked with `nonBlocking()`), deferring blocking resource resolution to future work to keep the initial changeset smaller and less complex.

PR Close #70211
2026-08-19 15:28:09 +00:00
arturovt abe019d505 fix(router): pass correct component to canDeactivate for named outlets in componentless parent routes
When a componentless parent route has children rendered into a named outlet
(e.g. `outlet: 'inner'`), the `canDeactivate` guard received `null` as the
component argument instead of the actual component instance.

The bug was in `deactivateRouteAndItsChildren`: for componentless routes,
each child was passed the same `context` inherited from the parent lookup,
which was `null` when the parent component only registered a named outlet.
The children's actual outlet contexts were never consulted.

The fix adds a `parentContexts: ChildrenOutletContexts | null` parameter so
that for componentless routes, each child is looked up by its own outlet name
in `parentContexts` (e.g. `parentContexts.getContext('inner')`). For
component routes, `context.children` is passed as the new `parentContexts`
on recursion, ensuring correct context resolution across component boundaries.

This re-addresses #34614. A previous fix (#36302) was reverted because it
passed `parentContexts` unchanged through component boundaries; this fix
updates `parentContexts` to `context.children` when descending into a
component route, preventing the wrong contexts from propagating into deeper
componentless levels.

Fixes #34614

(cherry picked from commit f1a4c85212)
2026-08-18 22:05:00 +00:00
Alon Mishne 223f25ff37 Revert "fix(router): limit protocol-relative URL handling to serialization"
This reverts commit 435f8b2b8b.

(cherry picked from commit 292991e2df)
2026-08-18 15:58:17 +00:00
Andrew Scott dc8c0717a0 refactor(router): Create transactional router resource
This commit adds an implementation of a router resource (not currently exposed for public use) which
defines the behavior of a resource dependent on the Router navigation lifecycle.

(cherry picked from commit c111f3fb0c)
2026-08-12 23:07:08 +00:00
SkyZeroZx 2f82601662 fix(router): limit protocol-relative URL handling to serialization
Preserve createUrlTree command semantics, including custom serializer inputs, while keeping the single-leading-slash guarantee at the default serialization boundary.

Expand coverage for command forms, public UrlTree values, secondary outlets, and preserved query parameters and fragments.

Fixes #69700

(cherry picked from commit 435f8b2b8b)
2026-08-11 19:07:35 +00:00
rootvector2 e5c37f21de fix(router): use safe hasOwnProperty when parsing query params
`parseQueryParam` and the AngularJS-compat `parseKeyValue` accumulate query params into a plain object and check key presence with `obj.hasOwnProperty`, so a `hasOwnProperty` query key clobbers the method and the next lookup throws `TypeError`. Switch both to `Object.hasOwn`, which can't be shadowed by a query key.
2026-07-07 10:09:34 -07:00
SkyZeroZx d9639201b3 test(router): Update tests to use currentNavigation()
Updates router integration tests to use the `currentNavigation()` method instead of deprecated `getCurrentNavigation`.

Also replaces direct `setTimeout` calls with the `timeout()` utility function.
2026-07-06 14:01:11 -07:00
arshsmith1 cbbb1d8ba1 fix(router): handle outlet named __proto__ in segment group maps
Outlet maps are keyed by names read verbatim from the url, so a name like
`__proto__` (e.g. `/one(__proto__:two)`) is assigned through the inherited
`__proto__` setter instead of creating an outlet. This drops the outlet and
mutates the map's prototype, and throws under Node's `--disable-proto=throw`.

Build these outlet maps with `Object.create(null)` so `__proto__` is treated as
an ordinary key. Covers `parseParens` and `squashSegmentGroup` in url_tree.ts,
`createSegmentGroup` in apply_redirects.ts, and `replaceSegment` and
`updateSegmentGroupChildren` in create_url_tree.ts.
2026-07-06 13:34:04 -07:00
arturovt 97a3fd6a55 feat(router): handle null and undefined inputs in RouterLinkActive
Without this change, components that use RouterLinkActive in multiple
contexts (e.g. both a navigation menu and body content) are forced to
branch the template for every conditional input:

  @if (activeClass) {
    <a [routerLink]="href" [routerLinkActive]="activeClass"
       [routerLinkActiveOptions]="activeOptions"
       [ariaCurrentWhenActive]="ariaCurrent">
      <ng-content />
    </a>
  } @else {
    <a [routerLink]="href"><ng-content /></a>
  }

Every additional input multiplies the branching, and each @if/@else
injects unwanted comment nodes into the DOM. There is no way to
conditionally attach a directive in Angular templates, making imperative
TypeScript instantiation the only alternative.

Accepting null/undefined collapses this to a single template branch:

  <a [routerLink]="href"
     [routerLinkActive]="activeClass"
     [routerLinkActiveOptions]="activeOptions"
     [ariaCurrentWhenActive]="ariaCurrent">
    <ng-content />
  </a>

When activeClass is undefined (e.g. in content areas), the directive
stays mounted but applies no CSS classes. When it is a string (e.g. in
the navigation), normal active-class behavior applies — no branching, no
extra DOM nodes, no TypeScript workarounds.

- `routerLinkActive`: null/undefined now sets an empty class list.

- `routerLinkActiveOptions`: null and undefined are treated differently:
  - undefined → falls back to the default subset match ("not set")
  - null → explicit opt-out, link is never considered active

Closes #66233
2026-06-24 12:15:49 -04:00
Andrew Scott 1e79dd3140 refactor(router): Add handling for ActivatedRoute-scoped injector
Add handling in navigation for creating and destroying injectors scoped
to `ActivatedRoute` life.
The code for creating the injectors is certainly more complicated
than it _could_ be since there's no actual feature built around this yet.

Keeps as much implementation code tree-shakeable as possible:
Raw size: +764 bytes
Gzipped size: +182 bytes
2026-06-17 11:39:13 -07:00
Andrew Scott fe721868a6 fix(router): use native URL object for navigation boundary and comparison
Previously, `NavigationStateManager` relied on string-based comparisons and `.substring()` to match `NavigateEvent` URLs against internal router transitions or the application root boundary. This was brittle against trailing slashes, query parameter order variations, and sibling application URLs.

This commit updates the logic to:

- Use the native `URL` object to strictly compare `origin` and `pathname` for `appRootURL` boundaries.

- Sort `searchParams` and use `Location.stripTrailingSlash()` to robustly compare the router destination against the event destination.

- Pre-compute and store `appRootUrl` as a `URL` object to avoid redundant parsing on every navigation.
2026-06-09 11:15:15 -07:00
arturovt 8ec0d1eee8 fix(router): skip scroll-to-top on initial navigation when hydrating
When scrollPositionRestoration is enabled and the app hydrates an
SSR-rendered page, RouterScroller was unconditionally scrolling the
viewport to [0, 0] on the first imperative navigation. This discards
any scroll position the user established while the server-rendered
page was loading.

Fix by injecting IS_HYDRATION_DOM_REUSE_ENABLED into RouterScroller
and suppressing the scroll-to-top for the initial navigation only.
Subsequent navigations are unaffected.

Closes #64578
2026-05-19 13:01:53 -07:00
Andrew Scott c84642ac16 feat(router): add unmatchedInputBehavior option to componentInputBinding
Introduce a new configuration option `unmatchedInputBehavior` to the `componentInputBinding` feature. This option allows users to configure the behavior when a component input is not matched by any key in the router data.

The available values are:
- 'alwaysUndefined': (Default) Always binds undefined to unmatched inputs.
- 'undefinedIfStale': Binds undefined only if the input was previously available in the router data for the active route in the outlet.

This feature addresses concerns raised in #63835 and #52946 regarding the retention of default values for inputs that were never targeted by the router, while still ensuring that stale data is cleared when a parameter is removed.
2026-05-01 16:04:52 -07:00
Matthieu Riegler 7f3f3d7da1 ci: remove remainings of saucelabs tests
Those haven't been used for a while.
2026-04-22 14:41:03 -07:00
SkyZeroZx 6eff439546 fix(router): restore internal URL on popstate when browserUrl is used
Fixed an issue where back/forward (`popstate`) navigation attempted to match the displayed `browserUrl` instead of the internal route, which could result in `NG04002: Cannot match any routes`.

Fixes #67549
2026-04-20 16:46:25 -07:00
Andrew Scott 17d10f7a99 fix(router): set default paramsInheritanceStrategy to 'always'
Set the default value of paramsInheritanceStrategy to 'always'. This change ensures that route parameters are inherited from parent routes by default, which is the behavior most users expect. It simplifies routing configuration for the majority of use cases.

This change aligns Angular with other popular routing systems where child routes automatically have access to parent parameters:
- React Router: useParams() includes parent params.
- Vue Router: $route.params includes parent params.
- Next.js: params are passed to nested layouts and pages.
- TanStack Router: useParams() includes parent params with full type safety.

BREAKING CHANGE: paramsInheritanceStrategy now defaults to 'always'

The default value of paramsInheritanceStrategy has been changed from 'emptyOnly' to 'always'. This means that route parameters are inherited from all parent routes by default. To restore the previous behavior, set paramsInheritanceStrategy to 'emptyOnly' in your router configuration.
2026-04-17 14:27:43 -07:00
arturovt c90b6b398e fix(router): normalize multiple leading slashes in URL parser
URLs with three or more consecutive leading slashes (e.g. `///test`) were
parsed incorrectly by `DefaultUrlSerializer`. The parser consumed only two
leading slashes, leaving a third that caused `parseSegment()` to produce an
empty `UrlSegment`. When serialized back, that empty segment rendered as
`//test` — a protocol-relative URL that browsers resolve as a different
origin and reject with a `SecurityError` when passed to
`history.pushState`/`replaceState`.

The fix changes `parseRootSegment()` to consume all consecutive leading
slashes instead of just one, normalizing any number of leading slashes to
a single `/` before the path is parsed.

Closes #49610
2026-04-14 12:34:03 +03:00
Doug Parker 0f6e850a65 test(router): remove addEventListener call count check
Creating a platform and bootstrapping an application might register more events than just what the router expects (and an event will be added to platform creation in this PR). This test shouldn't be so strict about it.
2026-04-13 14:12:48 +03:00
Andrew Scott daa9b2a9d6 fix(router): pass outlet context to split to fix empty path named outlets
The `split` helper function in `packages/router/src/utils/config_matching.ts` was blind to the current outlet being processed. When encountering an empty path named outlet in the config, it would assume it needed to pull it in as a synthetic empty group, even if we were already in the process of resolving that very outlet!

When navigating to `/(secondary:component-copy)` with this config:

```typescript
{
  path: '',
  component: MainLayout,
  children: [
    { path: '', outlet: 'secondary', component: SecondaryComponent, children: [{path: 'component-copy'}] }
  ]
}
```

The router uses `MainLayout` as a pass-through and calls `split` on its children with segments `['component-copy']`.
`split` uses the `containsEmptyPathMatchesWithNamedOutlets` helper to determine if there are any candidate empty path named outlets to pull in. Because of this, it sees `{ path: '', outlet: 'secondary' }` and says: "Ah, an empty path named outlet! I must pull it in!"
Rather than falling through to standard segment matching, it returns `UrlSegmentGroup(segments: [], children: {secondary: emptyGroup})`.
The router then tries to process `primary` (with `[]` segments) and fails because the config only has `secondary`. It also tries to process `secondary` with the `emptyGroup`. While `{ path: '', outlet: 'secondary' }` matches the empty group, its child `{ path: 'component-copy' }` fails to match because the `emptyGroup` has no segments! So both branches fail, resulting in a `NoMatch` error for the entire navigation!

Pulling in empty path named outlets IS desired when they act as siblings to segments we are matching. This has worked before and continues to work!

```typescript
{
  path: 'a',
  children: [
    { path: 'b', component: ComponentB },
    { path: '', component: ComponentC, outlet: 'aux' }
  ]
}
```

When navigating to `a/b`, `split` sees segments `['b']` and the `aux` empty path. It pulls in `aux` so it gets instantiated alongside `b`. This is correct!

If we have a named outlet with a non-empty path under an empty path parent:

```typescript
{
  path: '',
  component: MainLayout,
  children: [
    { path: 'component-copy', outlet: 'secondary', component: ComponentE }
  ]
}
```

When we navigate to `/(secondary:component-copy)`:
- `split` uses `containsEmptyPathMatchesWithNamedOutlets` to see if there are any empty path named outlets. Since it only sees `path: 'component-copy'`, it returns `false`.
- It falls through to standard segment matching, which finds `component-copy` in the segments array and activates it flawlessly!

This worked perfectly before the fix because it didn't use `containsEmptyPathMatchesWithNamedOutlets`.

The fix passes the **current active outlet context** into `split`. If `split` finds an empty path named outlet that matches the outlet we are already processing, it ignores it as a pull-in candidate.

When evaluating `MainLayout` children for `secondary`:
- URL Segments left to process: `['component-copy']`
- Current Outlet: `secondary`
- `childConfig`: `[{ path: '', outlet: 'secondary' }]`

Previously, `split` saw the empty path and pulled it in as a synthetic empty group, breaking matching. Now, since `getOutlet(r) === outlet` (both are `secondary`), the fix ignores it. Instead of returning empty segments, it **falls through to standard segment matching**, which successfully find the `component-copy` segment!

When evaluating `ComponentA` children for `primary`:
- URL Segments left to process: `['b']`
- Current Outlet: `primary`
- `childConfig`: `[{ path: 'b' }, { path: '', outlet: 'aux' }]`

Since `getOutlet(aux) !== primary`, the fix **does not ignore it**. `split` pulls in `aux: emptyGroup` as a sibling, instantiating `ComponentC` alongside `ComponentB`. This preserves correct behavior for auxiliary outlets!

fixes #67708
2026-04-01 11:48:42 +02:00
Jordan Webster 89c9a4de30 feat(router): Add options optional parameter for withComponentInputBinding
Add `ComponentInputBindingOptions` which is used with `withComponentInputBinding` and `bindToComponentInputs`
Can set which sources to bind as follows:
* queryParams
* params
* data

feat(router): Add `options` optional parameter for `withComponentInputBinding`

Add missing ternary operator for queryParams
2026-03-12 16:36:23 -06:00
Andrew Scott 778b748694 refactor(router): Permit deferring commit of traversal navigations
This updates the state manager to allow intercepting and deferring commits of traversal navigations.
The issues that were encountered in the past appear to be resolved in Chrome.
The behavior of redirect is still undefined in this case, so there is an added TODO.
2026-03-03 14:09:11 -08:00
Jaime Burgos 3683902234 feat(router): adds browserUrl input support to router links
Enables specifying a custom browser URL for router links via a new input,
allowing navigation to use an explicit browser URL in navigation options.
2026-02-24 10:51:33 -08:00
Matthieu Riegler bdb6ae9dbc refactor(router): remove deprecated provideRoutes function.
`provideRoutes` was deprecated in v15.

BREAKING CHANGE: `provideRoutes()` has been removed. Use `provideRouter()` or `ROUTES` as multi token if necessary.
2026-02-24 09:25:13 -08:00
Angular Robot a1501bc90a build: update rules_browsers digest to ceb5275
See associated pull request for more information.

Closes #67138 as a pr takeover
2026-02-19 15:47:47 -08:00
SkyZeroZx 0f47fda51b test(router): move timeout and autoTick helpers to shared testing utilities
Centralizes common test helpers under testing utilities and updates usages
2026-02-10 07:45:00 -08:00
Angular Robot 11767cabe4 build: update Jasmine to 6.0.0
Jasmine enables `forbidDuplicateNames: true` by default. So we also need to desambiguate duplicate spec names.
2026-02-09 12:15:57 -08:00
Jessica Janiuk 5a0f272519 Revert "feat(router): adds browserUrl input support to router links"
This reverts commit 9505541d32.
2026-02-02 16:32:09 -08:00
SkyZeroZx 68ba9c45cb test(router): remove provider zoneless from tests
Removes the `provideZonelessChangeDetection` provider from router tests.
It’s no longer needed and simplifies the test setup.
2026-02-02 15:00:18 -08:00
SkyZeroZx 9505541d32 feat(router): adds browserUrl input support to router links
Enables specifying a custom browser URL for router links via a new input,
allowing navigation to use an explicit browser URL in navigation options.

Closes #66805
2026-02-02 11:08:18 -08:00
Andrew Scott 458bc4a2c8 fix(router): limit UrlParser recursion depth to prevent stack overflow
Deeply nested parentheses in URLs (e.g. `(a/(b/(c...)))`) trigger recursive calls in `UrlParser`, which can lead to a `RangeError: Maximum call stack size exceeded`. While such errors are generally caught by the framework, relying on the runtime's stack limit is unpredictable across different environments and engine states (e.g. varying stack sizes in different browsers or Node.js versions).

The deeply nested parentheses  can cause a stack overflow. While such URLs can be valid (e.g., `(a/(b/(c...)))`) and serialize to simple paths (e.g., /a/b/c), excessive nesting is unreasonable and likely malicious or accidental.

Linear paths (e.g. /a/b/c/d) are parsed iteratively and do NOT trigger recursion. Only parentheses trigger recursion.

This commit introduces a recursion depth limit of 50. If parsing exceeds this depth, the router will now throw a specific `UNPARSABLE_URL` error with the message "URL is too deep". This ensures a deterministic failure mode that is easier for applications to handle than a crash or generic RangeError.

The limit of 50 is chosen as it should accommodate any reasonable application URL structure (including complex named outlets) while providing a safe upper bound against abusive payloads.

This is essentially a refactor of the error state:

* Before: RangeError (System says "I'm out of stack memory")
* After: RuntimeError (Validator says "Input is invalid")

This provides:

* Semantic Correctness: The error now correctly blames the input ("URL too deep"), not the environment ("Stack full").
* Cross-Platform Consistency: The limit is the same in Chrome, Firefox, Node, and Deno, regardless of their internal recursion limits.
* Fast Failure: We stop at depth 50 instead of depth ~15,000, saving those cycles (though CPU cost is negligible either way).

"wide" URLs are now theoretically more expensive than "deep" URLs (because deep ones fail fast), but both are well within safe bounds for any reasonable input size.
2026-01-29 12:15:21 -08:00
Andrew Scott 907a94dcec feat(router): Update IsActiveMatchOptions APIs to accept a Partial
This updates `RouterLinkActive`, `Router.isActive`, and the standalone
`isActive` function to accept `Partial<IsActiveMatchOptions>` which uses
the current default values as the base (paths and queryParams are
subset, fragment and matrix params are ignored).

fixes #53326
2026-01-29 12:10:40 -08:00
Andrew Scott cf9620f7d0 feat(router): Make match options optional in isActive
The behavior now matches RouterLinkActive.
2026-01-29 12:10:40 -08:00
Andrew Scott b51bab583d feat(router): Add partial ActivatedRouteSnapshot information to canMatch params
This commit adds partial `ActivatedRouteSnapshot` information as the
third parameter of the `canMatch` guard.

resolves #49309
2026-01-26 23:36:06 +00:00
Andrew Scott dbd50be7f7 fix(router): Do not intercept reload events with Navigation integration
This commit prevents the Router from intercepting reload navigations
in the navigate event listener. This would convert hard page reloads
to SPA navigations.

fixes #66746
2026-01-26 22:29:51 +00:00
Andrew Scott 8bbe6dc46c feat(common): Add Location strategies to manage trailing slash on write
Adds dedicated `LocationStrategy` subclasses: `NoTrailingSlashPathLocationStrategy` and `TrailingSlashPathLocationStrategy`.

The `TrailingSlashPathLocationStrategy` ensures that URLs prepared for the browser always end with a slash, while `NoTrailingSlashPathLocationStrategy` ensures they never do. This configuration only affects the URL written to the browser history; the `Location` service continues to normalize paths by stripping trailing slashes when reading from the browser.

Example:
```typescript
providers: [
  {provide: LocationStrategy, useClass: TrailingSlashPathLocationStrategy}
]
```

This approach to the trailing slash problem isolates the changes to the
existing LocationStrategy abstraction without changes to Router, as was
attempted in two other options (#66452 and #66423).

From an architectural perspective, this is the cleanest approach for several reasons:

1. Separation of Concerns and "Router Purity": The Router's primary job is to map a URL structure to an application state (ActivatedRoutes). It shouldn't necessarily be burdened with the formatting nuances of the underlying platform unless those nuances affect the state itself. By pushing trailing slash handling to the LocationStrategy, you treat the trailing slash as a "platform serialization format" rather than a "router state" concern. This avoids the "weirdness" in #66423 where the UrlTree (serialization format) disagrees with the ActivatedRouteSnapshot (logical state).

2. Tree Shakability: If an application doesn't care about trailing slashes (which is the default "never" behavior), they don't pay the cost for that logic. It essentially becomes a swappable "driver" for the URL interaction.

3. Simplicity for the Router: #66452 (consuming the slash as a segment) bleeds into the matching logic, potentially causing issues with child routes or wildcards effectively "eating" a segment that should be invisible. This option leaves the matching logic purely focused on meaningful path segments by continuing to strip the trailing slash on read.

4. Consistency with Existing Patterns: Angular already uses LocationStrategy to handle Hash vs Path routing. Adding "Trailing Slash" nuances there is a natural extension of that pattern—it's just another variation of "how do we represent this logic in the browser's address bar?"

fixes #16051
2026-01-23 20:09:23 +00:00
Andrew Scott e8c44a00f9 refactor(router): Retain original navigateEvent across redirects
This builds off of #66197 by retaining the original navigateEvent across redirects
so the NavigateEvent can more accurately track the lifecycle of a navigation,
which may span across several NavigationStart events due to redirects
2026-01-12 14:41:54 -08:00
Andrew Scott da364d2635 refactor(router): Add support for precommitHandler in Navigation integration
The `precommitHandler` of the Navigation API unlocks some of the truly
powerful features for Routers like Angular's which defer the URL
updates. Without the `precommitHandler`, we cannot initiate a navigation
until we are ready to commit the URL because it causes the URL to update
immediately.

With `precommitHandler` support, we are able to create a `NavigateEvent`
_immediately_ on navigation, which allows the browser to show that a
navigation is happening with a loading indicator. Site visitors will
also have the ability to cancel the navigation with the "stop" button.
When we are ready to commit the URL, the precommitHandler supports a
"redirect" function that we can use to first redirect the navigation to
a new location immediately before committing it.

The commit operation is not synchronous because the API waits for all
precommitHandlers to resolve. This commit adds a small bit of handling
to account for this so that the Router's transition does not advance
to the next stage until the URL has been committed.
2026-01-09 10:31:26 -08:00
Jessica Janiuk a2b9429992 Revert "feat(router): add trailingSlash config option"
This reverts commit 12fccc5e99.
2026-01-08 12:20:03 -08:00
Andrew Scott 12fccc5e99 feat(router): add trailingSlash config option
This commit introduces a highly requested `trailingSlash` configuration option to the Angular Router, allowing developers to control how trailing slashes are handled in their applications. The options are:
- 'always': Enforces a trailing slash on all URLs.
- 'never': Removes trailing slashes from all URLs (default).
- 'preserve': Respects the presence or absence of a trailing slash as defined in the UrlTree.
2026-01-08 08:26:37 -08:00
Andrew Scott 7003e8d241 feat(router): Publish Router's integration with platform Navigation API as experimental
This publishes the work that was done to integrate with the Navigation
API as an experimental router feature. Browser support is limited and in
active development. There are also known bugs in the browser implementations
and only Chromium browsers supported deferred URL updates with the
`precommitHandler`. Relates to #53321, which I would likely not mark as
completed until this is at least in dev preview, which likely won't
happen until it is widely available and potentially delayed until
`precommitHandler` is widely available as well.

The final form of this api might not even be a "router feature" in the end, but instead be
something similar to what other frameworks have to provide different
platform integrations (e.g. `provideNavigationRouter`). That would
support omitting the history-based integration from the bundle when only
the navigation integration is used. Alternatively, the current
`provideRouter` could require one of `withHistory` or `withPlatformNavigation`.
2026-01-07 16:16:06 -08:00
Andrew Scott bcef77d950 fix(router): Fix RouterLink href not updating with queryParamsHandling
There was a bug introduced in #60875. While RouterLink doesn't
necessarily depend on Router state, its link can change when navigations
cause the `ActivatedRoute` paths to change. It is difficult to determine
which segments the link depends on. Luckily, the commit had flawed
logic:

```
!this.queryParamsHandling && !dependsOnRouterState(this.options?.defaultQueryParamsHandling);
```

The subscription gets created whenever `queryParamsHandling` was not
defined, or it was non-default. This is pretty much all scenarios since
nobody is likely to set it explicitly to the default value. In addition,
the `click` handler recomputes the tree because `urlTree` is a getter
that does the computation.

This commit effectively rolls back #60875
2026-01-07 14:08:13 -05:00
Andreas Dorner 1c00ab42f8 feat(router): extend paramters of RedirectFunction to include paramMap and queryParamMap
adds paramMap and queryParamMap to the partial ActivatedRoute of the
RedirectFn

fixes ##60842
2026-01-07 12:36:54 -05:00
Andrew Scott 97fd1de0ac Revert "refactor(router): Add support for precommitHandler in Navigation integration"
This reverts commit 522fa716b8.
2026-01-07 11:45:58 -05:00
Andrew Scott 397dbc4c37 Revert "refactor(router): Retain original navigateEvent across redirects"
This reverts commit 53d3ae0feb.
2026-01-07 11:45:58 -05:00
Andrew Scott 174d2da29a Revert "fix(router): Ensure createUrlTree does not reuse segments of input"
This reverts commit 39efb62c0f.
2026-01-06 16:29:19 -08:00