Skip to content

Instantly share code, notes, and snippets.

@iffa
Last active May 16, 2024 20:48
Show Gist options
  • Save iffa/9c820072135d25a6372d58075fe264dd to your computer and use it in GitHub Desktop.
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'.
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];
}
}
@iffa
Copy link
Author

iffa commented Dec 18, 2021

@anbaran Thanks, updated code to reflect this.

@kepek
Copy link

kepek commented Oct 27, 2022

👍

@Chewieez
Copy link

Chewieez commented May 16, 2024

This may have been obvious to others but it wasn't to me. I had to change our app's router settings for scrollPositionRestoration to:
{ scrollPositionRestoration: 'disabled' } to get this workaround to work.

It took me a bit to figure that out. So if you are also wondering why this doesn't seem to work for you, check and make sure that is disabled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment