Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(router): restore 'history.state' on popstate even if navigationId missing #48033

Closed
wants to merge 3 commits into from

Conversation

nebkat
Copy link
Contributor

@nebkat nebkat commented Nov 11, 2022

If history.pushState() or history.replaceState() were called manually without including the navigationId field the state was being incorrectly discarded. That logic was meant for maintaining the original behavior of NavigationStart.restoredState, not for restoring the state to history.state.

Improves on #28176, fully fixes #28108, see also #28954.

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • angular.io application / infrastructure changes
  • Other... Please describe:

What is the current behavior?

history.replaceState({ foo: 'bar' });

location.back(); // Or browser button
location.forward();  // Or browser button

history.state; // contains { navigationId: ... }

Issue Number: #28954

What is the new behavior?

history.replaceState({ foo: 'bar' });

location.back(); // Or browser button
location.forward();  // Or browser button

history.state; // contains { navigationId: ..., foo: 'bar' }

Does this PR introduce a breaking change?

  • Yes
  • No

Only if app was relying on the broken behavior already.

Other information

See also #28954 (comment)

… missing

If `history.pushState()` or `history.replaceState()` were called manually without including the `navigationId` field the state was being incorrectly discarded - that logic was for maintaining the original behavior of `NavigationStart.restoredState`.

Improves on angular#28176, fully fixes angular#28108, see also angular#28954
@pullapprove pullapprove bot requested a review from atscott November 11, 2022 17:38
@ngbot ngbot bot added this to the Backlog milestone Nov 11, 2022
Copy link
Contributor

@atscott atscott left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using location.replaceState outside of the Router would break scroll restoration and the page id used to restore history correctly if a traversal navigation is rejected by guards. That said, I don't see any reason for the Router to blow away the state like it's doing now so this looks like a good change to me. Thanks!

Comment on lines 1024 to 1026
// NavigationStart.restoredState was originally set to either
// {{ navigationdId: number }} | null, so preserve this behavior
// even though we now include the rest of the state along with it (since #27198)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"restoredState is used in the NavigationStart event. The stored navigationId is used by the RouterScroller to retrieve the scroll position for the page."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've kept the original comment just to clarify that the behavior must be maintained for apps that rely on the { state } | null. RouterScroller uses navigationId but doesn't fail if it is missing.

Copy link
Contributor

@atscott atscott Nov 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't follow what you mean here, nor do I follow what the comment means to accomplish. The comment does not make the code more clear to me and actually makes it more confusing.

RouterScroller uses navigationId but doesn't fail if it is missing.

How is that possible? restoredState and navigationId is required to make scroll restoration work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original motivation for this fix was to preserve the contents of history.state if it is missing navigationId, which you agree with:

I don't see any reason for the Router to blow away the state like it's doing now

The only reason we do still blow away the state for NavigationStart.restoredState is to preserve the logic from pre-#27198.

Pre #27198: restoredState only contained navigationId, so it made sense to store null if it wasn't present.
Post #27198 restoreState contains the entire state, no longer makes sense to discard it (applying same logic as above).

Despite that, the logic must still be preserved in case an app has the following code:

if (e instanceof NavigationStart) {
    if (e.restoredState === null) doSomething();
}

The comment just makes that explicitly clear, since internal dependencies (i.e. RouterScroller) may change in future causing even more confusion.

How is that possible? restoredState and navigationId is required to make scroll restoration work.

Sorry what I meant by this was that RouterScroller does not throw an error if restoredState is missing navigationId. The scroll restoration of course doesn't work, but it silently reads undefined and ignores it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so I do agree partially with the goal of the comment. That said, right now it kind of reads like “this code was added in PR ‘x’. Changing it might/would break things.” Which is always true. Let’s instead make this actionable:

“TODO: restoredState should always include state if it exists, regardless of navigationId. This requires a breaking change to update the type on NavigationStart’s restoredState type, which requires navigationId to always present. The Router used to only restore history state if a navigationId were present.”

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, apologies for the confusion!

@atscott atscott added target: patch This PR is targeted for the next patch release action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews labels Nov 11, 2022
@cyraid
Copy link
Contributor

cyraid commented Nov 18, 2022

Any update on this?

Copy link
Contributor

@atscott atscott left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting review state to request changes to address comment

@nebkat
Copy link
Contributor Author

nebkat commented Nov 19, 2022

Would it be useful if I sent in more PRs for some of the router TODOs? I would love to help out further but I wouldn't want to waste your time reviewing minor improvements if it will require lots of extra discussion. I can think of some nice long term solutions to the state handling but they would involve deprecation - might be better left to you?

@atscott atscott added action: merge The PR is ready for merge by the caretaker and removed action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews labels Nov 20, 2022
@atscott
Copy link
Contributor

atscott commented Nov 20, 2022

Would it be useful if I sent in more PRs for some of the router TODOs? I would love to help out further but I wouldn't want to waste your time reviewing minor improvements if it will require lots of extra discussion. I can think of some nice long term solutions to the state handling but they would involve deprecation - might be better left to you?

I'm absolutely open to fielding more community PRs for the Router. I will say that many changes are pretty hard-fought, especially when they have to do with the timing of state management so just be prepared for some of them to be breaking in a way that might make the change very difficult to land. So just be prepared for things to be slow, not move forward, or even get reverted if failures were detected later on.
As for the state handling, if there's a change that leads us towards a better future but requires a deprecation, that's a definitely conversation worth having. Maybe you could start the conversations by opening an issue and we can discuss the changes ahead of time so you don't waste too much time on the implementation if you aren't sure we'd want to make the change.

@ChanduMdp
Copy link

Caretaker Note: Please ignore the google-internal-tests status. All targets passed after rerunning

@ChanduMdp ChanduMdp added the merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note label Nov 21, 2022
@cyraid
Copy link
Contributor

cyraid commented Nov 21, 2022

Would it be useful if I sent in more PRs for some of the router TODOs? I would love to help out further but I wouldn't want to waste your time reviewing minor improvements if it will require lots of extra discussion. I can think of some nice long term solutions to the state handling but they would involve deprecation - might be better left to you?

Thank you @nebkat for all your work on this. :)

@cyraid
Copy link
Contributor

cyraid commented Nov 21, 2022

Setting review state to request changes to address comment

And thank you @atscott for following up so quickly.

@dylhunn
Copy link
Contributor

dylhunn commented Nov 21, 2022

This PR was merged into the repository by commit 1976e37.

dylhunn pushed a commit that referenced this pull request Nov 21, 2022
… missing (#48033)

If `history.pushState()` or `history.replaceState()` were called manually without including the `navigationId` field the state was being incorrectly discarded - that logic was for maintaining the original behavior of `NavigationStart.restoredState`.

Improves on #28176, fully fixes #28108, see also #28954

PR Close #48033
@dylhunn dylhunn closed this in 1976e37 Nov 21, 2022
@nebkat nebkat deleted the router-history-state-improvements branch November 22, 2022 00:10
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Dec 23, 2022
trekladyone pushed a commit to trekladyone/angular that referenced this pull request Feb 1, 2023
… missing (angular#48033)

If `history.pushState()` or `history.replaceState()` were called manually without including the `navigationId` field the state was being incorrectly discarded - that logic was for maintaining the original behavior of `NavigationStart.restoredState`.

Improves on angular#28176, fully fixes angular#28108, see also angular#28954

PR Close angular#48033
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker area: router merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note target: patch This PR is targeted for the next patch release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

'history.state' object is getting lost when navigate using back/forward buttons
6 participants