Navigation Menu

Skip to content

Commit

Permalink
fix(router): eagerly update internal state on browser-triggered navig…
Browse files Browse the repository at this point in the history
…ations (#43102)

The management of `browserUrlTree` currently has several problems with
correctly tracking the actual state of the browser.

This change makes the Router eagerly update the `browserUrlTree` when
handling navigations triggered by browser events (i.e., not 'imperative'). This
is because with those types of navigations, the browser URL bar is
_already_ updated. If we do not update the internal tracking of the
`browserUrlTree`, we will be out of sync with the real URL if the
navigation is rejected.

It would be best if we could remove `browserUrlTree` completely, but doing that
would require a lot more investigation and is blocked by #27059 because
the SpyLocation used in tests does not emulate real browser behavior.

fixes #43101

PR Close #43102
  • Loading branch information
atscott authored and dylhunn committed Aug 16, 2021
1 parent b3bb6ec commit 286b280
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 12 deletions.
3 changes: 3 additions & 0 deletions packages/core/test/bundling/router/bundle.golden_symbols.json
Expand Up @@ -1526,6 +1526,9 @@
{
"name": "isArrayLike"
},
{
"name": "isBrowserTriggeredNavigation"
},
{
"name": "isCommandWithOutlets"
},
Expand Down
21 changes: 18 additions & 3 deletions packages/router/src/router.ts
Expand Up @@ -636,6 +636,12 @@ export class Router {
(this.onSameUrlNavigation === 'reload' ? true : urlTransition) &&
this.urlHandlingStrategy.shouldProcessUrl(t.rawUrl);

// If the source of the navigation is from a browser event, the URL is
// already updated. We already need to sync the internal state.
if (isBrowserTriggeredNavigation(t.source)) {
this.browserUrlTree = t.rawUrl;
}

if (processCurrentUrl) {
return of(t).pipe(
// Fire NavigationStart event
Expand Down Expand Up @@ -954,7 +960,12 @@ export class Router {
this.urlHandlingStrategy.merge(e.url, this.rawUrlTree);
const extras = {
skipLocationChange: t.extras.skipLocationChange,
replaceUrl: this.urlUpdateStrategy === 'eager'
// 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(
Expand Down Expand Up @@ -1385,8 +1396,8 @@ export class Router {
const lastNavigation = this.getTransition();
// We don't want to skip duplicate successful navs if they're imperative because
// onSameUrlNavigation could be 'reload' (so the duplicate is intended).
const browserNavPrecededByRouterNav =
source !== 'imperative' && lastNavigation?.source === 'imperative';
const browserNavPrecededByRouterNav = isBrowserTriggeredNavigation(source) && lastNavigation &&
!isBrowserTriggeredNavigation(lastNavigation.source);
const lastNavigationSucceeded = this.lastSuccessfulId === lastNavigation.id;
// If the last navigation succeeded or is in flight, we can use the rawUrl as the comparison.
// However, if it failed, we should compare to the final result (urlAfterRedirects).
Expand Down Expand Up @@ -1549,3 +1560,7 @@ function validateCommands(commands: string[]): void {
}
}
}

function isBrowserTriggeredNavigation(source: 'imperative'|'popstate'|'hashchange') {
return source !== 'imperative';
}
6 changes: 5 additions & 1 deletion packages/router/test/integration.spec.ts
Expand Up @@ -1230,7 +1230,11 @@ describe('Integration', () => {
location.simulateHashChange('/blocked');

advance(fixture);
expectEvents(recordedEvents, [[NavigationStart, '/blocked']]);
expectEvents(recordedEvents, [
[NavigationStart, '/blocked'],
[NavigationStart, '/simple'],
[NavigationEnd, '/simple'],
]);
}));
});

Expand Down
11 changes: 3 additions & 8 deletions packages/router/test/regression_integration.spec.ts
Expand Up @@ -266,14 +266,9 @@ describe('Integration', () => {
location.simulateHashChange('/one');
advance(fixture);

const BASE_ERROR_MESSAGE =
'This asserts current behavior, which is incorrect. When #28561 is fixed, it should be: ';

expect(location.path()).toEqual('/one', BASE_ERROR_MESSAGE + '/');
const urlChanges = ['replace: /', 'hash: /one'];
expect(location.urlChanges)
.toEqual(
urlChanges, BASE_ERROR_MESSAGE + JSON.stringify(urlChanges.concat('replace: /')));
expect(location.path()).toEqual('/');
const urlChanges = ['replace: /', 'hash: /one', 'replace: /'];
expect(location.urlChanges).toEqual(urlChanges);
}));
});
});
Expand Down

0 comments on commit 286b280

Please sign in to comment.