Skip to content

feat(common): Add location strategies to manage trailing slash on write#66490

Merged
atscott merged 1 commit intoangular:mainfrom
atscott:trailingSlashLocationOnly
Jan 23, 2026
Merged

feat(common): Add location strategies to manage trailing slash on write#66490
atscott merged 1 commit intoangular:mainfrom
atscott:trailingSlashLocationOnly

Conversation

@atscott
Copy link
Copy Markdown
Contributor

@atscott atscott commented 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:

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 feat(router): refine trailing slash handling with hybrid ephemeral/append strategy #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: feat(router): add trailingSlash config option #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

@angular-robot angular-robot bot added detected: feature PR contains a feature commit area: common Issues related to APIs in the @angular/common package labels Jan 12, 2026
@ngbot ngbot bot added this to the Backlog milestone Jan 12, 2026
@atscott atscott force-pushed the trailingSlashLocationOnly branch from 28634ce to 4c4de7d Compare January 12, 2026 20:08
@atscott atscott changed the title feat(common): replace REMOVE_TRAILING_SLASH with LocationStrategy subclasses feat(common): Add location strategies to manage trailing slash on write Jan 12, 2026
@atscott atscott force-pushed the trailingSlashLocationOnly branch from 4c4de7d to 77ac828 Compare January 12, 2026 20:15
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 atscott force-pushed the trailingSlashLocationOnly branch from 77ac828 to 8b4a8d1 Compare January 12, 2026 20:26
@atscott atscott marked this pull request as ready for review January 22, 2026 21:13
@pullapprove pullapprove bot requested review from JeanMeche and mmalerba January 22, 2026 21:13
@atscott atscott added the feature Label used to distinguish feature request from other issues label Jan 22, 2026
Copy link
Copy Markdown
Contributor

@mmalerba mmalerba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love how enterprise fizz buzz this feels. the fact that there's two entire classes to determine whether or not to have a slash 🤣

@pullapprove pullapprove bot requested a review from kirjs January 22, 2026 23:38
Copy link
Copy Markdown
Contributor

@mmalerba mmalerba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reviewed-for: public-api

@atscott atscott added action: merge The PR is ready for merge by the caretaker target: feature This PR is targeted for a feature branch (outside of main and semver branches) target: minor This PR is targeted for the next minor release and removed target: feature This PR is targeted for a feature branch (outside of main and semver branches) labels Jan 23, 2026
@atscott atscott merged commit 8bbe6dc into angular:main Jan 23, 2026
25 of 27 checks passed
@atscott
Copy link
Copy Markdown
Contributor Author

atscott commented Jan 23, 2026

This PR was merged into the repository. The changes were merged into the following branches:

@angular-automatic-lock-bot
Copy link
Copy Markdown

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Feb 23, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

action: merge The PR is ready for merge by the caretaker area: common Issues related to APIs in the @angular/common package detected: feature PR contains a feature commit feature Label used to distinguish feature request from other issues target: minor This PR is targeted for the next minor release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Routing: Preserve trailing slash

2 participants