Skip to content

Commit 1051c1d

Browse files
committed
fixup! refactor(@angular/ssr): combine redirect URL parameter helpers and use DefaultUrlSerializer
1 parent dc35d1e commit 1051c1d

2 files changed

Lines changed: 38 additions & 40 deletions

File tree

‎packages/angular/ssr/src/routes/ng-routes.ts‎

Lines changed: 26 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,22 @@ async function* handleRoute(options: {
178178

179179
const parentRoutes = new Map<string, RouteTreeNodeMetadata>();
180180

181+
if (typeof redirectTo === 'string') {
182+
let routeSegments: string[] | undefined;
183+
// Reference the route parameters by their position in the URL (see `buildUrlWithParams`).
184+
metadata.redirectTo = resolveRedirectTo(parentRoute, route, redirectTo, (parameter) => {
185+
routeSegments ??= joinUrlParts(baseHref, currentRoutePath).split('/');
186+
187+
return `*${routeSegments.lastIndexOf(parameter)}`;
188+
});
189+
metadata.relativeRedirect = redirectTo[0] !== '/' ? true : undefined;
190+
}
191+
181192
if (metadata.renderMode === RenderMode.Prerender) {
182193
for await (const result of handleSSGRoute(
183194
serverConfigRouteTree,
184195
route,
185196
parentRoute,
186-
baseHref,
187197
metadata,
188198
parentInjector,
189199
invokeGetPrerenderParams,
@@ -195,28 +205,16 @@ async function* handleRoute(options: {
195205
parentRoutes.set(result.route, result);
196206
}
197207
}
198-
} else if (redirectTo !== undefined) {
199-
if (metadata.status && !isValidRedirectResponseCode(metadata.status)) {
200-
yield {
201-
error:
202-
`The '${metadata.status}' status code is not a valid redirect response code. ` +
203-
`Please use one of the following redirect response codes: ${[...VALID_REDIRECT_RESPONSE_CODES.values()].join(', ')}.`,
204-
};
205-
} else if (typeof redirectTo === 'string') {
206-
let routeSegments: string[] | undefined;
207-
parentRoutes.set(metadata.route, {
208-
...metadata,
209-
// Reference the route parameters by their position in the URL (see `buildUrlWithParams`).
210-
redirectTo: resolveRedirectTo(parentRoute, route, redirectTo, (parameter) => {
211-
routeSegments ??= joinUrlParts(baseHref, currentRoutePath).split('/');
212-
213-
return `*${routeSegments.lastIndexOf(parameter)}`;
214-
}),
215-
relativeRedirect: redirectTo[0] !== '/' ? true : undefined,
216-
});
217-
} else {
218-
parentRoutes.set(metadata.route, metadata);
219-
}
208+
} else if (
209+
redirectTo !== undefined &&
210+
metadata.status &&
211+
!isValidRedirectResponseCode(metadata.status)
212+
) {
213+
yield {
214+
error:
215+
`The '${metadata.status}' status code is not a valid redirect response code. ` +
216+
`Please use one of the following redirect response codes: ${[...VALID_REDIRECT_RESPONSE_CODES.values()].join(', ')}.`,
217+
};
220218
} else {
221219
parentRoutes.set(metadata.route, metadata);
222220
}
@@ -433,7 +431,6 @@ function appendPreloadToMetadata(
433431
* @param serverConfigRouteTree - The tree representing the server's routing setup.
434432
* @param route - The route configuration.
435433
* @param parentRoute - The path of the parent route.
436-
* @param baseHref - The base href of the application.
437434
* @param metadata - The metadata associated with the route tree node.
438435
* @param parentInjector - The dependency injection container for the parent route.
439436
* @param invokeGetPrerenderParams - A flag indicating whether to invoke the `getPrerenderParams` function.
@@ -444,7 +441,6 @@ async function* handleSSGRoute(
444441
serverConfigRouteTree: RouteTree<ServerConfigRouteTreeAdditionalMetadata> | undefined,
445442
route: Route,
446443
parentRoute: string,
447-
baseHref: string,
448444
metadata: ServerConfigRouteTreeNodeMetadata,
449445
parentInjector: Injector,
450446
invokeGetPrerenderParams: boolean,
@@ -463,18 +459,6 @@ async function* handleSSGRoute(
463459
delete meta['getPrerenderParams'];
464460
}
465461

466-
const redirectTo = typeof route.redirectTo === 'string' ? route.redirectTo : undefined;
467-
if (redirectTo !== undefined) {
468-
let routeSegments: string[] | undefined;
469-
// Reference the route parameters by their position in the URL (see `buildUrlWithParams`).
470-
meta.redirectTo = resolveRedirectTo(parentRoute, route, redirectTo, (parameter) => {
471-
routeSegments ??= joinUrlParts(baseHref, currentRoutePath).split('/');
472-
473-
return `*${routeSegments.lastIndexOf(parameter)}`;
474-
});
475-
meta.relativeRedirect = redirectTo[0] !== '/' ? true : undefined;
476-
}
477-
478462
const isCatchAllRoute = CATCH_ALL_REGEXP.test(currentRoutePath);
479463
if (
480464
(isCatchAllRoute && !getPrerenderParams) ||
@@ -516,6 +500,7 @@ async function* handleSSGRoute(
516500
}
517501
}
518502

503+
const redirectTo = typeof route.redirectTo === 'string' ? route.redirectTo : undefined;
519504
const parameters = await runInInjectionContext(parentInjector, () => getPrerenderParams());
520505
try {
521506
for (const params of parameters) {
@@ -607,6 +592,10 @@ function resolveRedirectTo(
607592
redirectTo: string,
608593
resolveParam: (parameter: string) => string,
609594
): string {
595+
if (redirectTo[0] === '/' && (route.matcher || !redirectTo.includes('/:'))) {
596+
return redirectTo;
597+
}
598+
610599
const [path] = redirectTo.split(/[?#]/, 1);
611600
let resolvedPath = path;
612601

‎packages/angular/ssr/src/utils/url.ts‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ let urlSerializer: DefaultUrlSerializer | undefined;
164164
* placeholders in the query of `toUrl` from `fromSearch`, matching Angular router redirect semantics.
165165
*
166166
* - Each `*` path segment is replaced with the segment at the same index in `fromPath`.
167-
* - Each `*<n>` path segment is replaced with the segment at index `n` in `fromPath`.
167+
* - Each `*<n>` path segment is replaced with the 1-based segment at position `n` in `fromPath`.
168168
* - When `fromSearch` is provided, each query parameter whose value is `:name` is replaced with the
169169
* values of `name` from `fromSearch`, or `undefined` if missing. Repeated query parameters are not
170170
* resolved, matching the Angular router.
@@ -173,6 +173,13 @@ let urlSerializer: DefaultUrlSerializer | undefined;
173173
* @param fromPath - A base path string starting with `/` that provides segments for path placeholders.
174174
* @param fromSearch - Optional query string (e.g. `url.search`) that provides values for query placeholders.
175175
* @returns The URL with its path and query placeholders resolved.
176+
*
177+
* @example
178+
* ```ts
179+
* buildUrlWithParams('/*\/details', '/123/abc'); // '/123/details'
180+
* buildUrlWithParams('/user/*2/*1', '/a/b'); // '/user/b/a'
181+
* buildUrlWithParams('/find?q=:x', '/search', '?x=cats'); // '/find?q=cats'
182+
* ```
176183
*/
177184
export function buildUrlWithParams(toUrl: string, fromPath: string, fromSearch?: string): string {
178185
if (toUrl[0] !== '/') {
@@ -197,12 +204,14 @@ export function buildUrlWithParams(toUrl: string, fromPath: string, fromSearch?:
197204
...path
198205
.split('/')
199206
.map((part, index) =>
200-
part[0] === '*' ? fromPathParts[part.length === 1 ? index : +part.slice(1)] : part,
207+
part[0] === '*'
208+
? (fromPathParts[part.length === 1 ? index : +part.slice(1)] ?? part)
209+
: part,
201210
),
202211
);
203212
}
204213

205-
if (fromSearch !== undefined && suffix.includes('=:')) {
214+
if (fromSearch !== undefined && suffix[0] === '?' && suffix.includes('=:')) {
206215
urlSerializer ??= new DefaultUrlSerializer();
207216
const toTree = urlSerializer.parse(suffix);
208217
const actualParams = fromSearch ? urlSerializer.parse(fromSearch).queryParams : {};

0 commit comments

Comments
 (0)