feat(router): add trailingSlash config option#66276
Merged
thePunderWoman merged 2 commits intoangular:mainfrom Jan 8, 2026
Merged
feat(router): add trailingSlash config option#66276thePunderWoman merged 2 commits intoangular:mainfrom
thePunderWoman merged 2 commits intoangular:mainfrom
Conversation
57b5dad to
0472af6
Compare
00f336c to
9490c7e
Compare
miladj3
reviewed
Jan 6, 2026
miladj3
reviewed
Jan 6, 2026
miladj3
approved these changes
Jan 6, 2026
24c2aee to
949b72e
Compare
949b72e to
308fd49
Compare
JeanMeche
reviewed
Jan 6, 2026
JeanMeche
reviewed
Jan 6, 2026
JeanMeche
approved these changes
Jan 6, 2026
49a133a to
1dd866a
Compare
Member
|
(just pushed a rebase to solve the conflict and started the presubmit) |
1dd866a to
77c5e76
Compare
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.
Additionally, this commit updates the `defaultUrlMatcher` to optionally consume a single trailing empty path segment if the route otherwise matches. This ensures that routes with `pathMatch: 'full'` can correctly match URLs with trailing slashes (e.g., `/a/` matching `{path: 'a', pathMatch: 'full'}`), resolving a long-standing inconsistency where such routes would fail despite `pathMatch: 'prefix'` variants (with empty children) succeeding.
**Alternatives Considered & Design Rationale:**
* **`UrlTree.hasTrailingSlash` Property:**
We initially explored adding a explicit boolean property to `UrlTree`.
However, this would have required a significant public API change and breaking updates to custom `UrlSerializer` implementations to ensure the state was preserved. It also complicated the mental model of the `UrlTree` as a direct representation of the URL structure. We settled on representing trailing slashes as a trailing empty `UrlSegment`, which leverages the existing structure and serialization logic without API expansion.
**Why we chose the current approach:**
We decided on a "Permissive Matcher, Strict Serializer/Location" pattern.
- The **Matcher** is permissive: it allows consuming a trailing empty segment if it's the *only* thing left, treating it effectively as optional for `pathMatch: 'full'`. This aligns logic with how `pathMatch: 'prefix'` matches empty children.
- The **Serializer/Location** are strict: The `trailingSlash` configuration is verified at the boundaries (serialization and location normalization). If `trailingSlash: 'never'` is set, the slash is stripped *before* matching ever occurs. If matching sees a slash, it implies the configuration allowed it (or we are in a permissive mode), so the matcher should handle it gracefully.
fixes angular#16051
77c5e76 to
072bae4
Compare
Contributor
|
This PR was merged into the repository. The changes were merged into the following branches:
|
|
Better late than never! |
Contributor
|
We unfortunately had had to revert this change due to breakages internally. |
|
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This commit introduces a highly requested
trailingSlashconfiguration option to the Angular Router, allowing developers to control how trailing slashes are handled in their applications. The options are:Additionally, this commit updates the
defaultUrlMatcherto optionally consume a single trailing empty path segment if the route otherwise matches. This ensures that routes withpathMatch: 'full'can correctly match URLs with trailing slashes (e.g.,/a/matching{path: 'a', pathMatch: 'full'}), resolving a long-standing inconsistency where such routes would fail despitepathMatch: 'prefix'variants (with empty children) succeeding.Alternatives Considered & Design Rationale:
UrlTree.hasTrailingSlashProperty: We initially explored adding a explicit boolean property toUrlTree. However, this would have required a significant public API change and breaking updates to customUrlSerializerimplementations to ensure the state was preserved. It also complicated the mental model of theUrlTreeas a direct representation of the URL structure. We settled on representing trailing slashes as a trailing emptyUrlSegment, which leverages the existing structure and serialization logic without API expansion.Why we chose the current approach:
We decided on a "Permissive Matcher, Strict Serializer/Location" pattern.
pathMatch: 'full'. This aligns logic with howpathMatch: 'prefix'matches empty children.trailingSlashconfiguration is verified at the boundaries (serialization and location normalization). IftrailingSlash: 'never'is set, the slash is stripped before matching ever occurs. If matching sees a slash, it implies the configuration allowed it (or we are in a permissive mode), so the matcher should handle it gracefully.fixes #16051