Skip to content

Commit a2b9429

Browse files
Revert "feat(router): add trailingSlash config option"
This reverts commit 12fccc5.
1 parent 86dc128 commit a2b9429

15 files changed

Lines changed: 21 additions & 442 deletions

File tree

adev/src/content/guide/routing/customizing-route-behavior.md

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -127,20 +127,6 @@ provideRouter(routes, withRouterConfig({defaultQueryParamsHandling: 'merge'}));
127127

128128
This is especially helpful for search and filter pages to automatically retain existing filters when additional parameters are provided.
129129

130-
### Configure trailing slash handling
131-
132-
`trailingSlash` configures how the router and location service handle trailing slashes in URLs.
133-
134-
- `'always'`: Forces a trailing slash on all URLs.
135-
- `'never'`: Removes trailing slashes from all URLs.
136-
- `'preserve'`: Keeps the trailing slash if present, and omits it if not.
137-
138-
NOTE: By default, the `DefaultUrlSerializer` preserves trailing slashes, but `Location.path()` and `Location.normalize()` strip them.
139-
140-
```ts
141-
provideRouter(routes, withRouterConfig({trailingSlash: 'preserve'}));
142-
```
143-
144130
Angular Router exposes four main areas for customization:
145131

146132
<docs-pill-row>
@@ -238,7 +224,7 @@ When implementing a custom `RouteReuseStrategy`, you may need to manually destro
238224
Since `DetachedRouteHandle` is an opaque type, you cannot call a destroy method directly on it. Instead, use the `destroyDetachedRouteHandle` function provided by the Router.
239225

240226
```ts
241-
import {destroyDetachedRouteHandle} from '@angular/router';
227+
import { destroyDetachedRouteHandle } from '@angular/router';
242228

243229
// ... inside your strategy
244230
if (this.handles.size > MAX_CACHE_SIZE) {
@@ -259,10 +245,12 @@ By default, Angular does not destroy the injectors of detached routes, even if t
259245
To enable automatic cleanup of unused route injectors, you can use the `withExperimentalAutoCleanupInjectors` feature in your router configuration. This feature checks which routes are currently stored by the strategy after navigations and destroys the injectors of any detached routes that are not currently stored by the reuse strategy.
260246

261247
```ts
262-
import {provideRouter, withExperimentalAutoCleanupInjectors} from '@angular/router';
248+
import { provideRouter, withExperimentalAutoCleanupInjectors } from '@angular/router';
263249

264250
export const appConfig: ApplicationConfig = {
265-
providers: [provideRouter(routes, withExperimentalAutoCleanupInjectors())],
251+
providers: [
252+
provideRouter(routes, withExperimentalAutoCleanupInjectors())
253+
]
266254
};
267255
```
268256

goldens/public-api/common/index.api.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -948,9 +948,6 @@ export function provideNetlifyLoader(path?: string): Provider[];
948948
// @public
949949
export function registerLocaleData(data: any, localeId?: string | any, extraData?: any): void;
950950

951-
// @public
952-
export const REMOVE_TRAILING_SLASH: InjectionToken<boolean>;
953-
954951
// @public
955952
export class SlicePipe implements PipeTransform {
956953
// (undocumented)

goldens/public-api/router/index.api.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,6 @@ export function defaultUrlMatcher(segments: UrlSegment[], segmentGroup: UrlSegme
238238

239239
// @public
240240
export class DefaultUrlSerializer implements UrlSerializer {
241-
constructor();
242241
parse(url: string): UrlTree;
243242
serialize(tree: UrlTree): string;
244243
}
@@ -765,7 +764,6 @@ export interface RouterConfigOptions {
765764
onSameUrlNavigation?: OnSameUrlNavigation;
766765
paramsInheritanceStrategy?: 'emptyOnly' | 'always';
767766
resolveNavigationPromiseOnError?: boolean;
768-
trailingSlash?: 'always' | 'never' | 'preserve';
769767
urlUpdateStrategy?: 'deferred' | 'eager';
770768
}
771769

packages/common/src/location/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
export {HashLocationStrategy} from './hash_location_strategy';
10-
export {Location, PopStateEvent, REMOVE_TRAILING_SLASH} from './location';
10+
export {Location, PopStateEvent} from './location';
1111
export {APP_BASE_HREF, LocationStrategy, PathLocationStrategy} from './location_strategy';
1212
export {
1313
BrowserPlatformLocation,

packages/common/src/location/location.ts

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {Injectable, InjectionToken, OnDestroy, inject, ɵɵinject} from '@angular/core';
9+
import {Injectable, OnDestroy, ɵɵinject} from '@angular/core';
1010
import {Subject, SubscriptionLike} from 'rxjs';
1111

1212
import {LocationStrategy} from './location_strategy';
@@ -20,22 +20,6 @@ export interface PopStateEvent {
2020
url?: string;
2121
}
2222

23-
/**
24-
* A token that can be provided to configure whether the `Location` service should
25-
* strip trailing slashes from URLs.
26-
*
27-
* If `true`, the `Location` service will remove trailing slashes from URLs.
28-
* If `false`, the `Location` service will not remove trailing slashes from URLs.
29-
*
30-
* @publicApi
31-
*/
32-
export const REMOVE_TRAILING_SLASH = new InjectionToken<boolean>(
33-
typeof ngDevMode === 'undefined' || ngDevMode ? 'remove trailing slash' : '',
34-
{
35-
factory: () => true,
36-
},
37-
);
38-
3923
/**
4024
* @description
4125
*
@@ -51,22 +35,23 @@ export const REMOVE_TRAILING_SLASH = new InjectionToken<boolean>(
5135
* routing.
5236
*
5337
* `Location` is responsible for normalizing the URL against the application's base href.
54-
* A normalized URL is absolute from the URL host, includes the application's base href, and may
55-
* have a trailing slash:
38+
* A normalized URL is absolute from the URL host, includes the application's base href, and has no
39+
* trailing slash:
5640
* - `/my/app/user/123` is normalized
5741
* - `my/app/user/123` **is not** normalized
58-
* - `/my/app/user/123/` **is** normalized if `REMOVE_TRAILING_SLASH` is `false`
42+
* - `/my/app/user/123/` **is not** normalized
5943
*
6044
* ### Example
6145
*
6246
* {@example common/location/ts/path_location_component.ts region='LocationComponent'}
6347
*
64-
* @see {@link LocationStrategy}
65-
* @see [Routing and Navigation Guide](guide/routing/common-router-tasks)
66-
*
6748
* @publicApi
6849
*/
69-
@Injectable({providedIn: 'root', useFactory: createLocation})
50+
@Injectable({
51+
providedIn: 'root',
52+
// See #23917
53+
useFactory: createLocation,
54+
})
7055
export class Location implements OnDestroy {
7156
/** @internal */
7257
_subject = new Subject<PopStateEvent>();
@@ -78,17 +63,9 @@ export class Location implements OnDestroy {
7863
_urlChangeListeners: ((url: string, state: unknown) => void)[] = [];
7964
/** @internal */
8065
_urlChangeSubscription: SubscriptionLike | null = null;
81-
/** @internal */
82-
readonly _stripTrailingSlash: boolean;
8366

8467
constructor(locationStrategy: LocationStrategy) {
8568
this._locationStrategy = locationStrategy;
86-
try {
87-
this._stripTrailingSlash = inject(REMOVE_TRAILING_SLASH, {optional: true}) ?? true;
88-
} catch {
89-
// failsafe against not calling constructor in injection context
90-
this._stripTrailingSlash = true;
91-
}
9269
const baseHref = this._locationStrategy.getBaseHref();
9370
// Note: This class's interaction with base HREF does not fully follow the rules
9471
// outlined in the spec https://www.freesoft.org/CIE/RFC/1808/18.htm.
@@ -155,8 +132,7 @@ export class Location implements OnDestroy {
155132
* @returns The normalized URL string.
156133
*/
157134
normalize(url: string): string {
158-
const s = _stripBasePath(this._basePath, _stripIndexHtml(url));
159-
return this._stripTrailingSlash ? Location.stripTrailingSlash(s) : s;
135+
return Location.stripTrailingSlash(_stripBasePath(this._basePath, _stripIndexHtml(url)));
160136
}
161137

162138
/**

packages/common/test/location/location_spec.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
LocationStrategy,
1414
PathLocationStrategy,
1515
PlatformLocation,
16-
REMOVE_TRAILING_SLASH,
1716
} from '../../index';
1817
import {MockLocationStrategy, MockPlatformLocation} from '../../testing';
1918
import {TestBed} from '@angular/core/testing';
@@ -306,18 +305,4 @@ describe('Location Class', () => {
306305
expect(location.normalize(baseHref + path)).toBe(path);
307306
});
308307
});
309-
describe('Location with REMOVE_TRAILING_SLASH', () => {
310-
it('should strip trailing slash by default', () => {
311-
const location = TestBed.inject(Location);
312-
expect(location.normalize('/a/b/')).toBe('/a/b');
313-
});
314-
315-
it('should NOT strip trailing slash when REMOVE_TRAILING_SLASH is false', () => {
316-
TestBed.configureTestingModule({
317-
providers: [{provide: REMOVE_TRAILING_SLASH, useValue: false}],
318-
});
319-
const location = TestBed.inject(Location);
320-
expect(location.normalize('/a/b/')).toBe('/a/b/');
321-
});
322-
});
323308
});

packages/common/testing/src/location_mock.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ export class SpyLocation implements Location {
3535
_urlChangeListeners: ((url: string, state: unknown) => void)[] = [];
3636
/** @internal */
3737
_urlChangeSubscription: SubscriptionLike | null = null;
38-
/** @internal */
39-
_stripTrailingSlash = true;
4038

4139
/** @docs-private */
4240
ngOnDestroy(): void {

packages/core/test/bundling/router/bundle.golden_symbols.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,6 @@
236236
"REACTIVE_TEMPLATE_CONSUMER",
237237
"REMOVE_STYLES_ON_COMPONENT_DESTROY",
238238
"REMOVE_STYLES_ON_COMPONENT_DESTROY_DEFAULT",
239-
"REMOVE_TRAILING_SLASH",
240239
"RENDERER",
241240
"REQUIRED_UNSET_VALUE",
242241
"ROUTER_CONFIGURATION",

packages/router/src/provide_router.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
LocationStrategy,
1313
ViewportScroller,
1414
Location,
15-
REMOVE_TRAILING_SLASH,
1615
ɵNavigationAdapterForLocation,
1716
} from '@angular/common';
1817
import {
@@ -676,16 +675,7 @@ export type RouterConfigurationFeature =
676675
* @publicApi
677676
*/
678677
export function withRouterConfig(options: RouterConfigOptions): RouterConfigurationFeature {
679-
const providers = [
680-
{provide: ROUTER_CONFIGURATION, useValue: options},
681-
{
682-
provide: REMOVE_TRAILING_SLASH,
683-
useFactory: () => {
684-
const {trailingSlash} = options;
685-
return trailingSlash === 'always' || trailingSlash === 'preserve' ? false : true;
686-
},
687-
},
688-
];
678+
const providers = [{provide: ROUTER_CONFIGURATION, useValue: options}];
689679
return routerFeature(RouterFeatureKind.RouterConfigurationFeature, providers);
690680
}
691681

packages/router/src/router_config.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -110,18 +110,6 @@ export interface RouterConfigOptions {
110110
*/
111111
urlUpdateStrategy?: 'deferred' | 'eager';
112112

113-
/**
114-
* Configures how the `DefaultUrlSerializer` and `Location` service handle trailing slashes in URLs.
115-
*
116-
* - 'always': Forces a trailing slash on all URLs.
117-
* - 'never': Removes trailing slashes from all URLs.
118-
* - 'preserve': Keeps the trailing slash if present, and omits it if not.
119-
*
120-
* Note: By default, the `DefaultUrlSerializer` preserves trailing slashes, but `Location.path()`
121-
* and `Location.normalize()` strip them.
122-
*/
123-
trailingSlash?: 'always' | 'never' | 'preserve';
124-
125113
/**
126114
* The default strategy to use for handling query params in `Router.createUrlTree` when one is not provided.
127115
*

0 commit comments

Comments
 (0)