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

feat(router): implement scroll position restoration #20030

Closed
wants to merge 2 commits into from

Conversation

vsavkin
Copy link
Contributor

@vsavkin vsavkin commented Oct 30, 2017

No description provided.

@EreckGordon
Copy link

@vsavkin see https://github.com/angular/angular/pull/20030/files#diff-ff3b9c3c80dbf5eb828cf6d919746075R265

wahtItDoes <-- typo of what may cause this note to not appear in docs?

*/
export class RouterScrollManager implements OnDestroy {
private subscription: Subscription;
private windowObj: any = window;
Copy link
Member

Choose a reason for hiding this comment

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

Is this going to affect SSR?

Copy link

@josephliccini josephliccini Oct 30, 2017

Choose a reason for hiding this comment

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

I'm just creeping in random angular CRs again.. sorry.. one solution to that would be a NoopRouterScrollManager similar to NoopAnimationDriver

protected scrollIntoView(anchor: string, navigationEnd: NavigationEnd) {
if (this.options.anchorScrolling === 'enabled') {
const doc = this.dom.getDefaultDocument();
const el = this.dom.querySelector(doc.body, `#${anchor}`);
Copy link
Member

Choose a reason for hiding this comment

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

Browsers would also scroll to a[name="${anchor}"], so users might expect that here too.

@zimme
Copy link

zimme commented Oct 31, 2017

Does this take into account, async data loading and rendering?

Copy link
Contributor

@jasonaden jasonaden left a comment

Choose a reason for hiding this comment

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

Looks good overall. I added a couple comments to those already there.

private router: Router,
private options: {scrollPositionRestoration?: string, anchorScrolling?: string} = {}) {}

setUpScrollManager(): void {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not just have this happen in the constructor? As-is you could instantiate this class and end up running ngOnDestroy which will result in an error if setUpScrollManager() isn't called.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

bootstrapListener, where these things are set up, is pretty complex, so I prefer to explicitly invoke side effects, rather than rely on DI. That's why I prefer to keep the constructor side-effect free. Also, RouterPreloader works in the same way. I added an if statement into ngOnDestroy instead.

Copy link
Contributor

@jasonaden jasonaden Dec 5, 2017

Choose a reason for hiding this comment

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

👍

});
}

} else if (e instanceof NavigationEnd) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a possibility of this not working due to #17473? Where NavigationEnd can run before the UI has settled.

scan.call(this.router.events, (s: ScrollState, e: RouterEvent) => {
if (e instanceof NavigationStart) {
const navigationSource = e.navigationSource;
if (navigationSource === 'popstate') {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think there might be a problem with the logic here. Does the array get zeroed-out in this case (states are after NavigationEnd:

positions: [[0,0]]
source: imperative
restore: undefined
   --- scroll to 0, 10
   --- click link
positions: [[0,0], [0,10]]
source: popstate
restore: undefined
   --- back button
positions: [[0,0]]
source: popstate
restore: [0,10]
   --- forward button
positions: []
source: popstate
restore: [0,0]

/** @docsNotRequired */
url: string,
/** @docsNotRequired */
public navigationSource: NavigationSource = 'imperative') {
Copy link
Contributor

Choose a reason for hiding this comment

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

Add to NavigationEnd? You're not using it there in this PR, but seems like it should be there for consistency.

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 was thinking about it. The reason why I didn't go with this is that all navigations have NavigationStart, but not all of them have NavigationEnd. So if I add the source to NavigationEnd, I'll need to add it to NavigationError and NavigationCancel. It's a bigger API change, so I decided not to do it until the need is there. Especially because the user can always get the source by using groupBy.

But if you think this is required, I'll add it.

}
}, initState).subscribe((s: ScrollState) => {
if (s.restore && s.navigationEnd) {
this.restoreScrollPosition(s.restore, s.navigationEnd);
Copy link

Choose a reason for hiding this comment

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

What's the reason to pass in the NavigationEnd to your restoreScrollPosition API and not pass any RouterEvent to your getCurrentScrollPosition?

This ScrollManager covers the basic scenario for top-window scrolling, but since Angular advocates the usage of SPA, most likely I will be scrolling a DIV somewhere on the page on navigation (used with a router-outlet). Something like a left menu and a main page. Could we get this scenario covered a little easier?

It would be interesting to have a way to save multiple scroll positions of different DIV that are about to disappear. Then on re-appearing, restore all of them.

Choose a reason for hiding this comment

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

I think this would make the window jumping couple times, because of redirections int the routing configs.

@vsavkin vsavkin force-pushed the router_scrolling branch 3 times, most recently from 873bb55 to 7748c26 Compare November 17, 2017 17:11
@@ -13,6 +13,7 @@ import {LocationStrategy} from './location_strategy';
/** @experimental */
export interface PopStateEvent {
pop?: boolean;
state?: any;
Copy link
Contributor

Choose a reason for hiding this comment

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

Concrete types here would be great if it works for this use-case.

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 added types in the router package cause the router knows what is stores. The field here has to remain any because the location cannot depend on the router.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@MRosenlind
Copy link

Any news on this?

@jasonaden jasonaden added action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews feature Issue that requests a new feature labels Nov 30, 2017
@@ -193,6 +193,7 @@ export class DominoAdapter extends BrowserDomAdapter {

getHistory(): History { throw _notImplemented('getHistory'); }
getLocation(): Location { throw _notImplemented('getLocation'); }
getWindow(): Window { throw _notImplemented('getLocation'); }

Choose a reason for hiding this comment

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

Looks like this might need to pass a different string into the notImplemented error.

Copy link
Contributor

@jasonaden jasonaden left a comment

Choose a reason for hiding this comment

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

Looks good. Please rebase & push. We will need approval on the public API changes in order to get this merged.

@jasonaden jasonaden added action: merge The PR is ready for merge by the caretaker target: major This PR is targeted for the next major release 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 Dec 5, 2017
@IgorMinar
Copy link
Contributor

Reviewed in person with @jasonaden. LGTM now. Thank you for all the changes. Jason will still change the commit message to add a note about how to use this feature.

For documentation, see `RouterModule.scrollPositionRestoration`

Fixes angular#13636 angular#10929 angular#7791 angular#6595
@jasonaden jasonaden 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 Jun 8, 2018
* @whatItDoes Provides an empty implementation of the viewport scroller. This will
* live in @angular/common as it will be used by both platform-server and platform-webworker.
*/
export class NullViewportScroller implements ViewportScroller {
Copy link
Contributor

Choose a reason for hiding this comment

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

We have NoopAnimations. Should this follow the same naming? NoopViewport or so

@raysuelzer
Copy link

I see this is closed but not merged? Is this going to be merged?

@ericmartinezr
Copy link
Contributor

@raysuelzer Check the last feature https://github.com/angular/angular/blob/master/CHANGELOG.md#610-beta1-2018-06-13

@sarunint
Copy link
Contributor

@raysuelzer It's been merged. The way Angular do the merge is to cherry pick the commit into the master branch. So that the commit history is beautifully linear.

@jek-bao-choo
Copy link

jek-bao-choo commented Jun 25, 2018

May I know how do we use this feature?

@jek-bao-choo
Copy link

https://medium.com/@sarunint/scroll-position-restoration-in-angular-5a09df8bf626

@jek-bao-choo
Copy link

#24547

@devalmehta
Copy link

so is there a solution to this issue?

@MikeKovetsky
Copy link

Thank you for this feature, @vsavkin and Angular team.
Although, most of the features work as expected, I have trouble using scrollPositionRestoration: 'enabled'. with query params. The scrollPosition feature scrolls viewport to the top. This causes some UX problems on filter page.

When I switch scrollPositionRestoration to disabled it does not scroll and work as expected.
Is there a valid way to ignore queryParams change?

@Splaktar
Copy link
Member

@MikeKovetsky this appears to be tracked in #24547 (comment) now.

@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 Sep 14, 2019
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 cla: yes feature Issue that requests a new feature target: major This PR is targeted for the next major release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet