Skip to content
Open
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
71 changes: 44 additions & 27 deletions packages/angular/ssr/src/routes/ng-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,15 @@ import { AngularAppManifest, getAngularAppManifest } from '../manifest';
import { AngularBootstrap, isNgModule } from '../utils/ng';
import { promiseWithAbort } from '../utils/promise';
import { VALID_REDIRECT_RESPONSE_CODES, isValidRedirectResponseCode } from '../utils/redirect';
import { joinUrlParts, stripLeadingSlash } from '../utils/url';
import {
type PathSegment,
WILDCARD_SEGMENT,
joinUrlParts,
parsePathSegments,
parseUrl,
serializePathSegments,
stripLeadingSlash,
} from '../utils/url';
import {
PrerenderFallback,
RenderMode,
Expand Down Expand Up @@ -179,11 +187,17 @@ async function* handleRoute(options: {
const parentRoutes = new Map<string, RouteTreeNodeMetadata>();

if (typeof redirectTo === 'string') {
// Reference the route parameters by their position in the URL (see `buildUrlWithParams`).
metadata.redirectTo = resolveRedirectTo(parentRoute, route, redirectTo, (parameter) => {
const routeSegments = joinUrlParts(baseHref, currentRoutePath).split('/');
// Reference the route parameters by their 0-based position in the URL (see `buildUrlWithParams`).
const routeSegments = parsePathSegments(joinUrlParts(baseHref, currentRoutePath));
metadata.redirectTo = resolveRedirectTo(parentRoute, route, redirectTo, (paramName) => {
for (let i = routeSegments.length - 1; i >= 0; i--) {
const segment = routeSegments[i];
if (segment.type === 'param' && segment.name === paramName) {
return { type: 'wildcard', index: i };
}
}

return `*${routeSegments.lastIndexOf(parameter)}`;
return WILDCARD_SEGMENT;
});
Comment thread
alan-agius4 marked this conversation as resolved.
metadata.relativeRedirect = redirectTo[0] !== '/' ? true : undefined;
}
Expand Down Expand Up @@ -518,7 +532,7 @@ async function* handleSSGRoute(
parentRoute.replace(URL_PARAMETER_GLOBAL_REGEXP, replacer),
route,
redirectTo,
(parameter) => params[parameter.slice(1)],
(paramName) => ({ type: 'static', value: params[paramName] }),
),
};
}
Expand Down Expand Up @@ -581,45 +595,48 @@ function handlePrerenderParamsReplacement(
* @param parentRoute - The path of the parent route.
* @param route - The redirecting route.
* @param redirectTo - The target path for redirection.
* @param resolveParam - A function that returns the replacement for a parameter, such as `:id`.
* @param resolveParam - A function that returns the replacement `PathSegment` for a parameter name.
* @returns The resolved redirect path as a string.
* @throws If `redirectTo` uses a parameter that is not defined in the path of the route.
*/
function resolveRedirectTo(
parentRoute: string,
route: Route,
redirectTo: string,
resolveParam: (parameter: string) => string,
resolveParam: (paramName: string) => PathSegment,
): string {
if (redirectTo[0] === '/' && (route.matcher || !redirectTo.includes('/:'))) {
return redirectTo;
}

const [path] = redirectTo.split(/[?#]/, 1);
let resolvedPath = path;
const parsedRedirect = parseUrl(redirectTo);
let resolvedSegments = parsedRedirect.segments;

// The parameters of a custom matcher are only known at runtime.
if (!route.matcher && path.includes(':')) {
const routeSegments = route.path?.split('/') ?? [];
resolvedPath = path
.split('/')
.map((segment) => {
if (segment[0] !== ':') {
return segment;
}
if (!route.matcher && parsedRedirect.hasDynamicSegments) {
const routeSegments = parsePathSegments(route.path ?? '');
resolvedSegments = resolvedSegments.map((segment) => {
if (segment.type !== 'param') {
return segment;
}

if (!routeSegments.includes(segment)) {
throw new Error(`Cannot redirect to '${redirectTo}'. Cannot find '${segment}'.`);
}
if (!routeSegments.some((s) => s.type === 'param' && s.name === segment.name)) {
throw new Error(`Cannot redirect to '${redirectTo}'. Cannot find ':${segment.name}'.`);
}

return resolveParam(segment);
})
.join('/');
return resolveParam(segment.name);
});
}

return redirectTo[0] === '/'
? resolvedPath + redirectTo.slice(path.length)
: joinUrlParts(parentRoute.replace(URL_PARAMETER_GLOBAL_REGEXP, '*'), resolvedPath);
if (parsedRedirect.isAbsolute) {
return serializePathSegments(resolvedSegments) + parsedRedirect.rawSuffix;
}

const parentSegments = parsePathSegments(parentRoute).map((segment) =>
segment.type === 'param' ? WILDCARD_SEGMENT : segment,
);

return serializePathSegments([...parentSegments, ...resolvedSegments]);
}

/**
Expand Down
Loading
Loading