Skip to content

Commit 7136273

Browse files
atscottpkozlowski-opensource
authored andcommitted
refactor(router): compress synchronous end to router navigation to single operator
The end of the Router navigation is a block of synchronous logic that can be compressed into a single operator rather than splitting it across several, making it harder to step through. The only benefit from the split is automatic unsubscribe/cancellation, which we can replicate with an additional 'shouldContinue' check before proceeding. (cherry picked from commit b7d21e2)
1 parent 5a80a48 commit 7136273

File tree

3 files changed

+39
-57
lines changed

3 files changed

+39
-57
lines changed

packages/core/test/bundling/router/bundle.golden_symbols.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,6 @@
385385
"_stripOrigin",
386386
"_wasLastNodeCreated",
387387
"abortSignalToObservable",
388-
"activateRoutes",
389388
"activeConsumer",
390389
"addAfterRenderSequencesForView",
391390
"addEmptyPathsToChildrenIfNeeded",

packages/router/src/navigation_transition.ts

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ import {
5858
isRedirectingNavigationCancelingError,
5959
redirectingNavigationError,
6060
} from './navigation_canceling_error';
61-
import {activateRoutes} from './operators/activate_routes';
61+
import {ActivateRoutes} from './operators/activate_routes';
6262
import {checkGuards} from './operators/check_guards';
6363
import {recognize} from './operators/recognize';
6464
import {resolveData} from './operators/resolve_data';
@@ -725,6 +725,7 @@ export class NavigationTransitions {
725725

726726
switchTap(() => this.afterPreactivation()),
727727

728+
// TODO(atscott): Move this into the last block below.
728729
switchMap(() => {
729730
const {currentSnapshot, targetSnapshot} = overallTransitionState;
730731
const viewTransitionStarted = this.createViewTransition?.(
@@ -740,35 +741,56 @@ export class NavigationTransitions {
740741
: of(overallTransitionState);
741742
}),
742743

744+
// Ensure that if some observable used to drive the transition doesn't
745+
// complete, the navigation still finalizes This should never happen, but
746+
// this is done as a safety measure to avoid surfacing this error (#49567).
747+
take(1),
748+
743749
map((t: NavigationTransition) => {
744750
const targetRouterState = createRouterState(
745751
router.routeReuseStrategy,
746752
t.targetSnapshot!,
747753
t.currentRouterState,
748754
);
749-
this.currentTransition = overallTransitionState = {...t, targetRouterState};
755+
this.currentTransition = overallTransitionState = t = {...t, targetRouterState};
750756
this.currentNavigation.update((nav) => {
751757
nav!.targetRouterState = targetRouterState;
752758
return nav;
753759
});
754-
return overallTransitionState;
755-
}),
756760

757-
tap(() => {
758761
this.events.next(new BeforeActivateRoutes());
759-
}),
762+
if (!shouldContinueNavigation()) {
763+
return;
764+
}
760765

761-
activateRoutes(
762-
this.rootContexts,
763-
router.routeReuseStrategy,
764-
(evt: Event) => this.events.next(evt),
765-
this.inputBindingEnabled,
766-
),
766+
new ActivateRoutes(
767+
router.routeReuseStrategy,
768+
overallTransitionState.targetRouterState!,
769+
overallTransitionState.currentRouterState,
770+
(evt: Event) => this.events.next(evt),
771+
this.inputBindingEnabled,
772+
).activate(this.rootContexts);
773+
774+
if (!shouldContinueNavigation()) {
775+
return;
776+
}
767777

768-
// Ensure that if some observable used to drive the transition doesn't
769-
// complete, the navigation still finalizes This should never happen, but
770-
// this is done as a safety measure to avoid surfacing this error (#49567).
771-
take(1),
778+
completedOrAborted = true;
779+
this.currentNavigation.update((nav) => {
780+
(nav as Writable<Navigation>).abort = noop;
781+
return nav;
782+
});
783+
this.lastSuccessfulNavigation.set(untracked(this.currentNavigation));
784+
this.events.next(
785+
new NavigationEnd(
786+
t.id,
787+
this.urlSerializer.serialize(t.extractedUrl),
788+
this.urlSerializer.serialize(t.urlAfterRedirects!),
789+
),
790+
);
791+
this.titleStrategy?.updateTitle(t.targetRouterState!.snapshot);
792+
t.resolve(true);
793+
}),
772794

773795
takeUntil(
774796
abortSignalToObservable(abortController.signal).pipe(
@@ -785,23 +807,6 @@ export class NavigationTransitions {
785807
),
786808

787809
tap({
788-
next: (t: NavigationTransition) => {
789-
completedOrAborted = true;
790-
this.currentNavigation.update((nav) => {
791-
(nav as Writable<Navigation>).abort = noop;
792-
return nav;
793-
});
794-
this.lastSuccessfulNavigation.set(untracked(this.currentNavigation));
795-
this.events.next(
796-
new NavigationEnd(
797-
t.id,
798-
this.urlSerializer.serialize(t.extractedUrl),
799-
this.urlSerializer.serialize(t.urlAfterRedirects!),
800-
),
801-
);
802-
this.titleStrategy?.updateTitle(t.targetRouterState!.snapshot);
803-
t.resolve(true);
804-
},
805810
complete: () => {
806811
completedOrAborted = true;
807812
},
@@ -849,6 +854,7 @@ export class NavigationTransitions {
849854
}
850855
}),
851856
catchError((e) => {
857+
completedOrAborted = true;
852858
// If the application is already destroyed, the catch block should not
853859
// execute anything in practice because other resources have already
854860
// been released and destroyed.
@@ -857,7 +863,6 @@ export class NavigationTransitions {
857863
return EMPTY;
858864
}
859865

860-
completedOrAborted = true;
861866
/* This error type is issued during Redirect, and is handled as a
862867
* cancellation rather than an error. */
863868
if (isNavigationCancelingError(e)) {

packages/router/src/operators/activate_routes.ts

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,13 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {MonoTypeOperatorFunction} from 'rxjs';
10-
import {map} from 'rxjs/operators';
11-
129
import {ActivationEnd, ChildActivationEnd, Event} from '../events';
13-
import type {NavigationTransition} from '../navigation_transition';
1410
import type {DetachedRouteHandleInternal, RouteReuseStrategy} from '../route_reuse_strategy';
1511
import type {ChildrenOutletContexts} from '../router_outlet_context';
1612
import {ActivatedRoute, advanceActivatedRoute, RouterState} from '../router_state';
1713
import {nodeChildrenAsMap, TreeNode} from '../utils/tree';
1814

1915
let warnedAboutUnsupportedInputBinding = false;
20-
21-
export const activateRoutes = (
22-
rootContexts: ChildrenOutletContexts,
23-
routeReuseStrategy: RouteReuseStrategy,
24-
forwardEvent: (evt: Event) => void,
25-
inputBindingEnabled: boolean,
26-
): MonoTypeOperatorFunction<NavigationTransition> =>
27-
map((t) => {
28-
new ActivateRoutes(
29-
routeReuseStrategy,
30-
t.targetRouterState!,
31-
t.currentRouterState,
32-
forwardEvent,
33-
inputBindingEnabled,
34-
).activate(rootContexts);
35-
return t;
36-
});
37-
3816
export class ActivateRoutes {
3917
constructor(
4018
private routeReuseStrategy: RouteReuseStrategy,

0 commit comments

Comments
 (0)