Skip to content

Instantly share code, notes, and snippets.

@iffa
Last active August 21, 2023 11:05
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • 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];
}
}
@pokono
Copy link

pokono commented Feb 10, 2021

<3

@julkue
Copy link

julkue commented Mar 2, 2021

Thanks for sharing!

@FFKL
Copy link

FFKL commented May 31, 2021

Thanks!

@SiarheiBokuts
Copy link

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?

@anbaran
Copy link

anbaran commented Dec 15, 2021

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,

@iffa
Copy link
Author

iffa commented Dec 18, 2021

@anbaran Thanks, updated code to reflect this.

@kepek
Copy link

kepek commented Oct 27, 2022

👍

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