diff --git a/packages/angular/ssr/src/routes/router.ts b/packages/angular/ssr/src/routes/router.ts index 715a56b5753a..f01e9989028e 100644 --- a/packages/angular/ssr/src/routes/router.ts +++ b/packages/angular/ssr/src/routes/router.ts @@ -7,7 +7,7 @@ */ import { AngularAppManifest } from '../manifest'; -import { stripIndexHtmlFromURL } from '../utils/url'; +import { stripIndexHtmlFromURL, stripMatrixParams } from '../utils/url'; import { extractRoutesAndCreateRouteTree } from './ng-routes'; import { RouteTree, RouteTreeNodeMetadata } from './route-tree'; @@ -85,8 +85,10 @@ export class ServerRouter { match(url: URL): RouteTreeNodeMetadata | undefined { // Strip 'index.html' from URL if present. // A request to `http://www.example.com/page/index.html` will render the Angular route corresponding to `http://www.example.com/page`. - const { pathname } = stripIndexHtmlFromURL(url); + let { pathname } = stripIndexHtmlFromURL(url); + pathname = stripMatrixParams(pathname); + pathname = decodeURIComponent(pathname); - return this.routeTree.match(decodeURIComponent(pathname)); + return this.routeTree.match(pathname); } } diff --git a/packages/angular/ssr/src/utils/url.ts b/packages/angular/ssr/src/utils/url.ts index 9d42ddd6db00..9b5edede7f8e 100644 --- a/packages/angular/ssr/src/utils/url.ts +++ b/packages/angular/ssr/src/utils/url.ts @@ -196,3 +196,27 @@ export function buildPathWithParams(toPath: string, fromPath: string): string { return joinUrlParts(...resolvedParts); } + +const MATRIX_PARAMS_REGEX = /;[^/]+/g; + +/** + * Removes Angular matrix parameters from a given URL path. + * + * This function takes a URL path string and removes any matrix parameters. + * Matrix parameters are parts of a URL segment that start with a semicolon `;`. + * + * @param pathname - The URL path to remove matrix parameters from. + * @returns The URL path with matrix parameters removed. + * + * @example + * ```ts + * stripMatrixParams('/path;param=value'); // returns '/path' + * stripMatrixParams('/path;param=value/to;p=1/resource'); // returns '/path/to/resource' + * stripMatrixParams('/path/to/resource'); // returns '/path/to/resource' + * ``` + */ +export function stripMatrixParams(pathname: string): string { + // Use a regular expression to remove matrix parameters. + // This regex finds all occurrences of a semicolon followed by any characters + return pathname.includes(';') ? pathname.replace(MATRIX_PARAMS_REGEX, '') : pathname; +} diff --git a/packages/angular/ssr/test/utils/url_spec.ts b/packages/angular/ssr/test/utils/url_spec.ts index b6fcd4e7e767..9a7a7cb3ad49 100644 --- a/packages/angular/ssr/test/utils/url_spec.ts +++ b/packages/angular/ssr/test/utils/url_spec.ts @@ -13,6 +13,7 @@ import { joinUrlParts, stripIndexHtmlFromURL, stripLeadingSlash, + stripMatrixParams, stripTrailingSlash, } from '../../src/utils/url'; @@ -181,4 +182,30 @@ describe('URL Utils', () => { }).toThrowError(`Invalid toPath: The string must start with a '/'. Received: 'details'`); }); }); + + describe('stripMatrixParams', () => { + it('should remove a single matrix parameter', () => { + expect(stripMatrixParams('/path;param=value')).toBe('/path'); + }); + + it('should remove multiple matrix parameters in the same segment', () => { + expect(stripMatrixParams('/path;p1=v1;p2=v2')).toBe('/path'); + }); + + it('should remove matrix parameters from multiple segments', () => { + expect(stripMatrixParams('/path;p1=v1/to;p2=v2/resource')).toBe('/path/to/resource'); + }); + + it('should not modify a path without matrix parameters', () => { + expect(stripMatrixParams('/path/to/resource')).toBe('/path/to/resource'); + }); + + it('should handle a root path with matrix parameters', () => { + expect(stripMatrixParams('/;p1=v1')).toBe('/'); + }); + + it('should handle an empty string', () => { + expect(stripMatrixParams('')).toBe(''); + }); + }); });