MCPcopy Create free account
hub / github.com/angular/angular / createTransactionalSnapshot

Function createTransactionalSnapshot

packages/router/src/router_resource.ts:115–184  ·  view source on GitHub ↗

* Creates a signal that tracks the resource snapshot and handles transactional behavior * (freezing during navigation and rollback recovery).

(
  source: Resource<T>,
  router: Router,
  injector: Injector,
)

Source from the content-addressed store, hash-verified

113 * (freezing during navigation and rollback recovery).
114 */
115function createTransactionalSnapshot<T>(
116 source: Resource<T>,
117 router: Router,
118 injector: Injector,
119): {
120 snapshot: Signal<ResourceSnapshot<T>>;
121 frozenSnapshot: Signal<ResourceSnapshot<T> | null>;
122} {
123 // Holds a snapshot of the resource to keep the UI masked (frozen) during pending navigations
124 // or while recovering from a cancelled navigation.
125 const frozenSnapshot = signal<ResourceSnapshot<T> | null>(null);
126
127 // Tracks whether we are in a recovery phase after a cancelled navigation.
128 // The intended behavior is that on cancellation, the router reverts to the previous state.
129 // This reversion might trigger a new load of the previous state because the signal dependencies
130 // changed. If we were to release the frozen resource state immediately, the user would see a loading state
131 // for data they were just looking at. To avoid this "loading flash", we retain the frozen
132 // value (via frozenSnapshot) during this recovery load/reload until the resource settles.
133 const isRollbackRecoveryPending = signal(false);
134
135 const sub = router.events.subscribe((e) => {
136 if (e instanceof NavigationStart) {
137 isRollbackRecoveryPending.set(false);
138
139 if (frozenSnapshot() === null) {
140 // Freeze the snapshot at the start of navigation to keep the UI stable.
141 frozenSnapshot.set(source.snapshot());
142 }
143 } else if (e instanceof NavigationEnd) {
144 // Navigation succeeded, so we can unfreeze and use the live state.
145 frozenSnapshot.set(null);
146 isRollbackRecoveryPending.set(false);
147 } else if (e instanceof NavigationSkipped) {
148 // If a navigation is skipped while we have a frozen snapshot (e.g. navigating to the
149 // current URL to cancel an in-flight navigation), the in-flight navigation is aborted
150 // and parameter rollback begins. We must maintain the frozen snapshot until the rollback
151 // recovery load completes to prevent flashing a loading state.
152 if (frozenSnapshot() !== null) {
153 isRollbackRecoveryPending.set(true);
154 }
155 } else if (e instanceof NavigationCancel || e instanceof NavigationError) {
156 const isRollback =
157 e instanceof NavigationError ||
158 (e instanceof NavigationCancel &&
159 e.code !== NavigationCancellationCode.SupersededByNewNavigation &&
160 e.code !== NavigationCancellationCode.Redirect);
161
162 if (!isRollback) return;
163
164 isRollbackRecoveryPending.set(true);
165 }
166 });
167
168 injector.get(DestroyRef).onDestroy(() => sub.unsubscribe());
169
170 effect(
171 () => {
172 if (isRollbackRecoveryPending() && hasValueOrResolved(source)) {

Callers 1

routerResourceFunction · 0.85

Calls 10

signalFunction · 0.90
effectFunction · 0.90
computedFunction · 0.90
hasValueOrResolvedFunction · 0.85
snapshotMethod · 0.80
subscribeMethod · 0.65
setMethod · 0.65
onDestroyMethod · 0.65
getMethod · 0.65
unsubscribeMethod · 0.65

Tested by

no test coverage detected