Skip to content

Commit

Permalink
refactor(router): Remove unnecessary setTimeout in UrlTree redirects (#…
Browse files Browse the repository at this point in the history
…45735)

Using `setTimeout` in the Router navigation pipeline creates fragile and
unpredictable behavior. Additionally, it creates a new macrotask, which
would trigger an unnecessary change detection in the application.

This `setTimeout` was added in
15e3978.
Both tests added in that commit still pass. Additionally, the comment
for _why_ the `setTimeout` was added doesn't really explain how the
described bug would occur. There has been a lot of work in the Router
since then to stabalize edge case scenarios so it's possible it existed
before but doesn't anymore.

Removing this `setTimeout` revealed tests that
relied on the navigation not completing. For example, the test suite did
not have a route which matched the redirect, but the test passed because
it ended before the redirect was flushed, so the `Router` never threw an
error. Similar situations exist for the other use of `setTimeout` in the Route
(the one in the location change listener).
There were no other failures in TGP other than incorrectly written
tests.

BREAKING CHANGE:
When a guard returns a `UrlTree`, the router would previously schedule
the redirect navigation within a `setTimeout`. This timeout is now removed,
which can result in test failures due to incorrectly written tests.
Tests which perform navigations should ensure that all timeouts are
flushed before making assertions. Tests should ensure they are capable
of handling all redirects from the original navigation.

PR Close #45735
  • Loading branch information
atscott committed Apr 27, 2022
1 parent f1cc4a6 commit 7b367d9
Showing 1 changed file with 15 additions and 21 deletions.
36 changes: 15 additions & 21 deletions packages/router/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1000,27 +1000,21 @@ export class Router {
if (!redirecting) {
t.resolve(false);
} else {
// setTimeout is required so this navigation finishes with
// the return EMPTY below. If it isn't allowed to finish
// processing, there can be multiple navigations to the same
// URL.
setTimeout(() => {
const mergedTree =
this.urlHandlingStrategy.merge(e.url, this.rawUrlTree);
const extras = {
skipLocationChange: t.extras.skipLocationChange,
// The URL is already updated at this point if we have 'eager' URL
// updates or if the navigation was triggered by the browser (back
// button, URL bar, etc). We want to replace that item in history if
// the navigation is rejected.
replaceUrl: this.urlUpdateStrategy === 'eager' ||
isBrowserTriggeredNavigation(t.source)
};

this.scheduleNavigation(
mergedTree, 'imperative', null, extras,
{resolve: t.resolve, reject: t.reject, promise: t.promise});
}, 0);
const mergedTree =
this.urlHandlingStrategy.merge(e.url, this.rawUrlTree);
const extras = {
skipLocationChange: t.extras.skipLocationChange,
// The URL is already updated at this point if we have 'eager' URL
// updates or if the navigation was triggered by the browser (back
// button, URL bar, etc). We want to replace that item in history if
// the navigation is rejected.
replaceUrl: this.urlUpdateStrategy === 'eager' ||
isBrowserTriggeredNavigation(t.source)
};

this.scheduleNavigation(
mergedTree, 'imperative', null, extras,
{resolve: t.resolve, reject: t.reject, promise: t.promise});
}

/* All other errors should reset to the router's internal URL reference to
Expand Down

0 comments on commit 7b367d9

Please sign in to comment.