@@ -31,7 +31,15 @@ import { AngularAppManifest, getAngularAppManifest } from '../manifest';
3131import { AngularBootstrap , isNgModule } from '../utils/ng' ;
3232import { promiseWithAbort } from '../utils/promise' ;
3333import { VALID_REDIRECT_RESPONSE_CODES , isValidRedirectResponseCode } from '../utils/redirect' ;
34- import { joinUrlParts , stripLeadingSlash } from '../utils/url' ;
34+ import {
35+ type PathSegment ,
36+ WILDCARD_SEGMENT ,
37+ joinUrlParts ,
38+ parsePathSegments ,
39+ parseUrl ,
40+ serializePathSegments ,
41+ stripLeadingSlash ,
42+ } from '../utils/url' ;
3543import {
3644 PrerenderFallback ,
3745 RenderMode ,
@@ -179,11 +187,17 @@ async function* handleRoute(options: {
179187 const parentRoutes = new Map < string , RouteTreeNodeMetadata > ( ) ;
180188
181189 if ( typeof redirectTo === 'string' ) {
182- // Reference the route parameters by their position in the URL (see `buildUrlWithParams`).
183- metadata . redirectTo = resolveRedirectTo ( parentRoute , route , redirectTo , ( parameter ) => {
184- const routeSegments = joinUrlParts ( baseHref , currentRoutePath ) . split ( '/' ) ;
190+ // Reference the route parameters by their 0-based position in the URL (see `buildUrlWithParams`).
191+ const routeSegments = parsePathSegments ( joinUrlParts ( baseHref , currentRoutePath ) ) ;
192+ metadata . redirectTo = resolveRedirectTo ( parentRoute , route , redirectTo , ( paramName ) => {
193+ for ( let i = routeSegments . length - 1 ; i >= 0 ; i -- ) {
194+ const segment = routeSegments [ i ] ;
195+ if ( segment . type === 'param' && segment . name === paramName ) {
196+ return { type : 'wildcard' , index : i } ;
197+ }
198+ }
185199
186- return `* ${ routeSegments . lastIndexOf ( parameter ) } ` ;
200+ return WILDCARD_SEGMENT ;
187201 } ) ;
188202 metadata . relativeRedirect = redirectTo [ 0 ] !== '/' ? true : undefined ;
189203 }
@@ -518,7 +532,7 @@ async function* handleSSGRoute(
518532 parentRoute . replace ( URL_PARAMETER_GLOBAL_REGEXP , replacer ) ,
519533 route ,
520534 redirectTo ,
521- ( parameter ) => params [ parameter . slice ( 1 ) ] ,
535+ ( paramName ) => ( { type : 'static' , value : params [ paramName ] } ) ,
522536 ) ,
523537 } ;
524538 }
@@ -581,45 +595,48 @@ function handlePrerenderParamsReplacement(
581595 * @param parentRoute - The path of the parent route.
582596 * @param route - The redirecting route.
583597 * @param redirectTo - The target path for redirection.
584- * @param resolveParam - A function that returns the replacement for a parameter, such as `:id` .
598+ * @param resolveParam - A function that returns the replacement `PathSegment` for a parameter name .
585599 * @returns The resolved redirect path as a string.
586600 * @throws If `redirectTo` uses a parameter that is not defined in the path of the route.
587601 */
588602function resolveRedirectTo (
589603 parentRoute : string ,
590604 route : Route ,
591605 redirectTo : string ,
592- resolveParam : ( parameter : string ) => string ,
606+ resolveParam : ( paramName : string ) => PathSegment ,
593607) : string {
594608 if ( redirectTo [ 0 ] === '/' && ( route . matcher || ! redirectTo . includes ( '/:' ) ) ) {
595609 return redirectTo ;
596610 }
597611
598- const [ path ] = redirectTo . split ( / [ ? # ] / , 1 ) ;
599- let resolvedPath = path ;
612+ const parsedRedirect = parseUrl ( redirectTo ) ;
613+ let resolvedSegments = parsedRedirect . segments ;
600614
601615 // The parameters of a custom matcher are only known at runtime.
602- if ( ! route . matcher && path . includes ( ':' ) ) {
603- const routeSegments = route . path ?. split ( '/' ) ?? [ ] ;
604- resolvedPath = path
605- . split ( '/' )
606- . map ( ( segment ) => {
607- if ( segment [ 0 ] !== ':' ) {
608- return segment ;
609- }
616+ if ( ! route . matcher && parsedRedirect . hasDynamicSegments ) {
617+ const routeSegments = parsePathSegments ( route . path ?? '' ) ;
618+ resolvedSegments = resolvedSegments . map ( ( segment ) => {
619+ if ( segment . type !== 'param' ) {
620+ return segment ;
621+ }
610622
611- if ( ! routeSegments . includes ( segment ) ) {
612- throw new Error ( `Cannot redirect to '${ redirectTo } '. Cannot find '${ segment } '.` ) ;
613- }
623+ if ( ! routeSegments . some ( ( s ) => s . type === 'param' && s . name === segment . name ) ) {
624+ throw new Error ( `Cannot redirect to '${ redirectTo } '. Cannot find ': ${ segment . name } '.` ) ;
625+ }
614626
615- return resolveParam ( segment ) ;
616- } )
617- . join ( '/' ) ;
627+ return resolveParam ( segment . name ) ;
628+ } ) ;
618629 }
619630
620- return redirectTo [ 0 ] === '/'
621- ? resolvedPath + redirectTo . slice ( path . length )
622- : joinUrlParts ( parentRoute . replace ( URL_PARAMETER_GLOBAL_REGEXP , '*' ) , resolvedPath ) ;
631+ if ( parsedRedirect . isAbsolute ) {
632+ return serializePathSegments ( resolvedSegments ) + parsedRedirect . rawSuffix ;
633+ }
634+
635+ const parentSegments = parsePathSegments ( parentRoute ) . map ( ( segment ) =>
636+ segment . type === 'param' ? WILDCARD_SEGMENT : segment ,
637+ ) ;
638+
639+ return serializePathSegments ( [ ...parentSegments , ...resolvedSegments ] ) ;
623640}
624641
625642/**
0 commit comments