-
Notifications
You must be signed in to change notification settings - Fork 24.9k
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
Conversation
140f3c0
to
7f05e91
Compare
@vsavkin see https://github.com/angular/angular/pull/20030/files#diff-ff3b9c3c80dbf5eb828cf6d919746075R265
|
7f05e91
to
c256ba5
Compare
*/ | ||
export class RouterScrollManager implements OnDestroy { | ||
private subscription: Subscription; | ||
private windowObj: any = window; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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}`); |
There was a problem hiding this comment.
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.
Does this take into account, async data loading and rendering? |
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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') { |
There was a problem hiding this comment.
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]
packages/router/src/events.ts
Outdated
/** @docsNotRequired */ | ||
url: string, | ||
/** @docsNotRequired */ | ||
public navigationSource: NavigationSource = 'imperative') { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
873bb55
to
7748c26
Compare
@@ -13,6 +13,7 @@ import {LocationStrategy} from './location_strategy'; | |||
/** @experimental */ | |||
export interface PopStateEvent { | |||
pop?: boolean; | |||
state?: any; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
7748c26
to
3dcd424
Compare
Any news on this? |
@@ -193,6 +193,7 @@ export class DominoAdapter extends BrowserDomAdapter { | |||
|
|||
getHistory(): History { throw _notImplemented('getHistory'); } | |||
getLocation(): Location { throw _notImplemented('getLocation'); } | |||
getWindow(): Window { throw _notImplemented('getLocation'); } |
There was a problem hiding this comment.
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.
3dcd424
to
ab86195
Compare
There was a problem hiding this 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.
ab86195
to
68c660a
Compare
ac7f99b
to
8ec1002
Compare
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
8ec1002
to
f0f9c04
Compare
* @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 { |
There was a problem hiding this comment.
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
I see this is closed but not merged? Is this going to be merged? |
@raysuelzer Check the last feature https://github.com/angular/angular/blob/master/CHANGELOG.md#610-beta1-2018-06-13 |
@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. |
May I know how do we use this feature? |
so is there a solution to this issue? |
Thank you for this feature, @vsavkin and Angular team. When I switch |
@MikeKovetsky this appears to be tracked in #24547 (comment) now. |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
No description provided.