feat(router): add trailingSlash config option#66452
Closed
atscott wants to merge 2 commits intoangular:mainfrom
Closed
feat(router): add trailingSlash config option#66452atscott wants to merge 2 commits intoangular:mainfrom
atscott wants to merge 2 commits intoangular:mainfrom
Conversation
Route matchers now execute within the route's injection context.
6f01bb9 to
c13e69a
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
c13e69a to
561e83c
Compare
atscott
added a commit
to atscott/angular
that referenced
this pull request
Jan 12, 2026
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 (angular#66452 and angular#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 angular#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: angular#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 angular#16051
atscott
added a commit
to atscott/angular
that referenced
this pull request
Jan 12, 2026
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 (angular#66452 and angular#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 angular#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: angular#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 angular#16051
atscott
added a commit
that referenced
this pull request
Jan 23, 2026
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
|
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 to
UrlTree.However, this would have required a significant public API change and breaking updates to custom
UrlSerializerimplementations 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