Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/angular/ssr/src/routes/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import { AngularAppManifest } from '../manifest';
import { stripIndexHtmlFromURL, stripMatrixParams } from '../utils/url';
import { stripAuxiliaryRoutes, stripIndexHtmlFromURL, stripMatrixParams } from '../utils/url';
import { extractRoutesAndCreateRouteTree } from './ng-routes';
import { RouteTree, RouteTreeNodeMetadata } from './route-tree';

Expand Down Expand Up @@ -87,6 +87,7 @@ export class ServerRouter {
// A request to `http://www.example.com/page/index.html` will render the Angular route corresponding to `http://www.example.com/page`.
let { pathname } = stripIndexHtmlFromURL(url);
pathname = stripMatrixParams(pathname);
pathname = stripAuxiliaryRoutes(pathname);
pathname = decodeURIComponent(pathname);

return this.routeTree.match(pathname);
Expand Down
35 changes: 35 additions & 0 deletions packages/angular/ssr/src/utils/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,38 @@ export function stripMatrixParams(pathname: string): string {
export function constructUrl(pathname: string, search: string, hash: string): string {
return decodeURIComponent([stripTrailingSlash(pathname), search, hash].join(''));
}

/**
* Removes Angular auxiliary route segments from a given URL path.
*
* Auxiliary routes have the format `(outlet:segment)` appended to a primary URL.
* Multiple auxiliary routes and even nested forms will be removed entirely.
*
* @param pathname - The URL path from which to remove auxiliary route segments.
* @returns The cleaned URL path without auxiliary outlet routes.
*
* @example
* ```ts
* // Single auxiliary route
* stripAuxiliaryRoutes('/inbox/33(popup:compose)');
* // → '/inbox/33'
*
* // Multiple auxiliary routes
* stripAuxiliaryRoutes('/mail/7(popup:compose)(drawer:details)(chat:room42)');
* // → '/mail/7'
*
* // Nested auxiliary routes (rare but possible)
* stripAuxiliaryRoutes('/inbox(overlay:view(popup:info))(tracker:read)');
* // → '/inbox'
*
* // Path without auxiliary routes remains unchanged
* stripAuxiliaryRoutes('/path/to/resource');
* // → '/path/to/resource'
* ```
*/

export function stripAuxiliaryRoutes(pathname: string): string {
const index = pathname.indexOf('(');

return index !== -1 ? pathname.slice(0, index) : pathname;
}
21 changes: 21 additions & 0 deletions packages/angular/ssr/test/utils/url_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
addTrailingSlash,
buildPathWithParams,
joinUrlParts,
stripAuxiliaryRoutes,
stripIndexHtmlFromURL,
stripLeadingSlash,
stripMatrixParams,
Expand Down Expand Up @@ -208,4 +209,24 @@ describe('URL Utils', () => {
expect(stripMatrixParams('')).toBe('');
});
});

describe('stripAuxiliaryRoutes', () => {
it('should remove a single auxiliary route', () => {
expect(stripAuxiliaryRoutes('/inbox/33(popup:compose)')).toBe('/inbox/33');
});

it('should remove multiple auxiliary routes', () => {
expect(stripAuxiliaryRoutes('/mail/7(popup:compose)(drawer:details)(chat:room42)')).toBe(
'/mail/7',
);
});

it('should remove nested auxiliary routes', () => {
expect(stripAuxiliaryRoutes('/inbox(overlay:view(popup:info))(tracker:read)')).toBe('/inbox');
});

it('should not modify a path without auxiliary routes', () => {
expect(stripAuxiliaryRoutes('/path/to/resource')).toBe('/path/to/resource');
});
});
});