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
This commit is contained in:
Andrew Scott 2026-01-02 10:40:29 -08:00 committed by Kirill Cherkashin
parent 10da2f9029
commit 39efb62c0f
2 changed files with 22 additions and 1 deletions

View file

@ -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);
}

View file

@ -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<unknown>) {