From 39efb62c0f4d72e7ca72ca458083b54fb76eec2d Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Fri, 2 Jan 2026 10:40:29 -0800 Subject: [PATCH] fix(router): Ensure createUrlTree does not reuse segments of input Mutating the `UrlTree` that is returned by `createUrlTree` can cause the input, which might even be an active route, to be mutated. This ensures the `UrlSegment`s are recreated and do not mutate the input. fixes #54624 --- packages/router/src/url_tree.ts | 5 ++++- packages/router/test/create_url_tree.spec.ts | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/router/src/url_tree.ts b/packages/router/src/url_tree.ts index 1f73b4d0be7..751bf6f2426 100644 --- a/packages/router/src/url_tree.ts +++ b/packages/router/src/url_tree.ts @@ -794,7 +794,10 @@ export function squashSegmentGroup(segmentGroup: UrlSegmentGroup): UrlSegmentGro newChildren[childOutlet] = childCandidate; } } - const s = new UrlSegmentGroup(segmentGroup.segments, newChildren); + const s = new UrlSegmentGroup( + segmentGroup.segments.map((s) => new UrlSegment(s.path, {...s.parameters})), + newChildren, + ); return mergeTrivialChildren(s); } diff --git a/packages/router/test/create_url_tree.spec.ts b/packages/router/test/create_url_tree.spec.ts index e50d1693d9f..665e0f98344 100644 --- a/packages/router/test/create_url_tree.spec.ts +++ b/packages/router/test/create_url_tree.spec.ts @@ -758,6 +758,24 @@ describe('createUrlTreeFromSnapshot', () => { await advance(fixture); expect(router.url).toEqual('/parent/sibling'); }); + it('should not affect the input snapshot when the result is mutated', async () => { + TestBed.configureTestingModule({ + providers: [provideRouter([{path: 'a', children: [{path: 'b', component: class {}}]}])], + }); + const router = TestBed.inject(Router); + await router.navigateByUrl('/a/b'); + const snapshot = router.routerState.snapshot.root.firstChild!; + + // createUrlTreeFromSnapshot will squash the segment group + const tree = createUrlTreeFromSnapshot(snapshot, []); + + // Mutate the result's segments array (e.g. simulating what applyRedirects might do) + tree.root.children[PRIMARY_OUTLET].segments[0].path = 'c'; + + // The input snapshot should remain unchanged + expect(snapshot.url.length).toBe(1); + expect(snapshot.url[0].path).toBe('a'); + }); }); async function advance(fixture: ComponentFixture) {