Last active
August 21, 2023 11:05
-
-
Save iffa/9c820072135d25a6372d58075fe264dd to your computer and use it in GitHub Desktop.
Custom scroll position restoration logic for Angular 2+, that doesn't consider query parameter changes in route as forward navigation, thus preventing certain scenarios where you don't want query parameter changes to scroll-to-top as they would with 'scrollPositionRestoration: enabled'.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export class AppModule { | |
constructor(private router: Router, private viewportScroller: ViewportScroller) { | |
// Disable automatic scroll restoration to avoid race conditions | |
this.viewportScroller.setHistoryScrollRestoration('manual'); | |
this.handleScrollOnNavigation(); | |
} | |
/** | |
* When route is changed, Angular interprets a simple query params change as "forward navigation" too. | |
* Using the pairwise function allows us to have both the previous and current router events, which we can | |
* use to effectively compare the two navigation events and see if they actually change route, or only | |
* the route parameters (i.e. selections stored in query params). | |
* | |
* Related to: https://github.com/angular/angular/issues/26744 | |
*/ | |
private handleScrollOnNavigation(): void { | |
this.router.events.pipe( | |
// import { Event } from '@angular/router' | |
filter((e: Event): e is Scroll => e instanceof Scroll), | |
pairwise() | |
).subscribe((e: Scroll[]) => { | |
const previous = e[0]; | |
const current = e[1]; | |
if (current.position) { | |
// Backward navigation | |
this.viewportScroller.scrollToPosition(current.position); | |
} else if (current.anchor) { | |
// Anchor navigation | |
this.viewportScroller.scrollToAnchor(current.anchor); | |
} else { | |
// Check if routes match, or if it is only a query param change | |
if (this.getBaseRoute(previous.routerEvent.urlAfterRedirects) !== this.getBaseRoute(current.routerEvent.urlAfterRedirects)) { | |
// Routes don't match, this is actual forward navigation | |
// Default behavior: scroll to top | |
this.viewportScroller.scrollToPosition([0, 0]); | |
} | |
} | |
}); | |
} | |
private getBaseRoute(url: string): string { | |
// return url without query params | |
return url.split('?')[0]; | |
} | |
} |
Thanks for sharing!
Thanks!
thanks! but I have one question. is it ok to not unsubscribe from this subscription in case of just frontend app / in case of the app with server-side rendering?
It is also working for me, thanks!
I have one more improvement. Add the following line before calling this.handleScrollOnNavigation();
this.viewportScroller.setHistoryScrollRestoration('manual');
This way automatic scrolling from browser will be turned off so there are no scrolling results race conditions. In my case on navigating back I first saw scroll to top even before route change and before this.viewportScroller.scrollToPosition(current.position);
was called,
@anbaran Thanks, updated code to reflect this.
👍
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
<3