Skip to content

Commit 5bf5e5f

Browse files
alan-agius4jkrems
authored andcommitted
fix(@angular/ssr): prioritize the first matching route over subsequent ones
Ensures that the SSR router gives precedence to the first matching route, addressing the issue where later conflicting routes. This change prevents the incorrect prioritization of routes and ensures the intended route is matched first, aligning routing behavior. Closes: #29539 (cherry picked from commit 6448f80)
1 parent 2dec1e7 commit 5bf5e5f

File tree

2 files changed

+49
-7
lines changed

2 files changed

+49
-7
lines changed

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

+15-7
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,6 @@ export async function getRoutesFromAngularRouterConfig(
557557
// Wait until the application is stable.
558558
await applicationRef.whenStable();
559559

560-
const routesResults: RouteTreeNodeMetadata[] = [];
561560
const errors: string[] = [];
562561

563562
let baseHref =
@@ -581,11 +580,12 @@ export async function getRoutesFromAngularRouterConfig(
581580
if (errors.length) {
582581
return {
583582
baseHref,
584-
routes: routesResults,
583+
routes: [],
585584
errors,
586585
};
587586
}
588587

588+
const routesResults: RouteTreeNodeMetadata[] = [];
589589
if (router.config.length) {
590590
// Retrieve all routes from the Angular router configuration.
591591
const traverseRoutes = traverseRoutesConfig({
@@ -599,11 +599,19 @@ export async function getRoutesFromAngularRouterConfig(
599599
entryPointToBrowserMapping,
600600
});
601601

602-
for await (const result of traverseRoutes) {
603-
if ('error' in result) {
604-
errors.push(result.error);
605-
} else {
606-
routesResults.push(result);
602+
const seenRoutes: Set<string> = new Set();
603+
for await (const routeMetadata of traverseRoutes) {
604+
if ('error' in routeMetadata) {
605+
errors.push(routeMetadata.error);
606+
continue;
607+
}
608+
609+
// If a result already exists for the exact same route, subsequent matches should be ignored.
610+
// This aligns with Angular's app router behavior, which prioritizes the first route.
611+
const routePath = routeMetadata.route;
612+
if (!seenRoutes.has(routePath)) {
613+
routesResults.push(routeMetadata);
614+
seenRoutes.add(routePath);
607615
}
608616
}
609617

packages/angular/ssr/test/routes/ng-routes_spec.ts

+34
Original file line numberDiff line numberDiff line change
@@ -570,4 +570,38 @@ describe('extractRoutesAndCreateRouteTree', () => {
570570
expect(errors).toHaveSize(0);
571571
expect(routeTree.toObject()).toHaveSize(2);
572572
});
573+
574+
it('should give precedence to the first matching route over subsequent ones', async () => {
575+
setAngularAppTestingManifest(
576+
[
577+
{
578+
path: '',
579+
children: [
580+
{ path: 'home', component: DummyComponent },
581+
{ path: '**', component: DummyComponent },
582+
],
583+
},
584+
// The following routes should be ignored due to Angular's routing behavior:
585+
// - ['', '**'] and ['**'] are equivalent, and the first match takes precedence.
586+
// - ['', 'home'] and ['home'] are equivalent, and the first match takes precedence.
587+
{
588+
path: 'home',
589+
redirectTo: 'never',
590+
},
591+
{
592+
path: '**',
593+
redirectTo: 'never',
594+
},
595+
],
596+
[{ path: '**', renderMode: RenderMode.Server }],
597+
);
598+
599+
const { routeTree, errors } = await extractRoutesAndCreateRouteTree({ url });
600+
expect(errors).toHaveSize(0);
601+
expect(routeTree.toObject()).toEqual([
602+
{ route: '/', renderMode: RenderMode.Server },
603+
{ route: '/home', renderMode: RenderMode.Server },
604+
{ route: '/**', renderMode: RenderMode.Server },
605+
]);
606+
});
573607
});

0 commit comments

Comments
 (0)