1

I will preface this by saying that there are several answers online for this exact same purpose with an Angular app. However, none of these appear to be working for me. I have so far tried nearly everything from these issues 1 , 2 , 3 , 4 and a couple of other but I am still not getting the correct behavior.

Currently, if I am scrolled down and viewing a route, then I click on the item to view a new route via routerLink, the page stays in the same position. This causes some issues especially when on a smaller screen or when looking down a long list of items.

In the current app, my AppComponent is as follows (I am using Angular Material):

<app-header></app-header>
<mat-sidenav-container class="main-sidenav">

  <mat-sidenav class="sidenav"#sidenav [mode]="mode" [opened]="openSidenav">
    <app-sidenav></app-sidenav>
  </mat-sidenav>
  <mat-sidenav-content>

    <router-outlet></router-outlet>

  </mat-sidenav-content>
</mat-sidenav-container>

The router-outlet is displaying one of the following:

  • A workout page with a list and filter
  • A workout "detail page" that shows the workout description

In the workout list, when an item is clicked, it has a routerLink: <a [routerLink]="[i]"> where i is the index of the current workout coming from an array (from a firebase database in my case).

Here is the stackblitz to this code. The workouts-page can be found at src/app/features/workouts-page/ and then the workout-list, workout-filter, and workout-detail showing up accordingly. However, when clicked on the routerLink, the scroll position stays the same on the workout detail page.

Since Angular 6+, the recommended method has been to use, within your routing module:

...
  imports: [RouterModule.forRoot(routes,{scrollPositionRestoration: 'top'})
...

At the same time, you could also use "enabled" and get the same result. This does not change any behavior in the application I am running. Furthermore, I have tried a few "work-arounds" such as adding the following to my main component:

export class MyAppComponent implements OnInit {
    constructor(private router: Router) { }

    ngOnInit() {
        this.router.events.subscribe((evt) => {
            if (!(evt instanceof NavigationEnd)) {
                return;
            }
            window.scrollTo(0, 0)
        });
    }
}

This workaround and many others have once again not changed the behavior and it leaves the page in the same scroll position as before the router was clicked. One different method from the GitHub request is to add an (activate)="onActivate($event) to the <router-outlet>, where the function runs window.scrollTo(0, 0);. Still, this doesn't seem to be working. I can even put the window.scrollTo(0,0) or scrollTop() in the ngOnInit of any component related to the router outlet, and you guessed it, still no change.

I figured this had to do with the css of the page altogether so I changed my body and html positioning as mentioned in stack overflow issues with the same end result.

Is there a way that I can "reset" the scroll position when a route is clicked with the routerLink property so the page doesn't stay in the same scrolled position when the route is loaded?

Jeremy
  • 1,038
  • 11
  • 34

2 Answers2

1

ScrollPositionRestoration works properly with full page scroll (body getting scrolled). In your case internal divs are scrollable. To restore scroll for those you need to customize the scroll restore process. Here is a blog link which talks about the same https://www.bennadel.com/blog/3534-restoring-and-resetting-the-scroll-position-using-the-navigationstart-event-in-angular-7-0-4.htm

Base idea is to storage scroll position before navigation and restore them if you visit the page due to pop state.

Nikhil Walvekar
  • 510
  • 3
  • 9
  • Thank you , this is a good solution albeit slightly complicated. I ended up finding a simpler solution but your answer gave a great method for customizing the ScrollPositionRestore. Thank you – Jeremy Mar 03 '20 at 15:28
0

Nikhil gave a method for the ScrollPositionRestoration and helped me find the reason this wasn't working, but I ended up finding a way to do this with a simple activate function.

Like he mentioned, the reason this wasn't working is due to the "body" being reset on the scroll. Since the fixed header was at the top of the body, it was already in the "top" scroll position all the time. To fix this, I added the following simple code:

In the AppComponent HTML

...
  <mat-sidenav-content id="detail">
    <router-outlet (activate)="resetPosition();"></router-outlet>
  </mat-sidenav-content>
...

Note, the (activate)="resetPosition(); causes the function to run any time a router is activated (or any time you go to a new router). Here is the function added to the app.component.ts:

AppComponent TS

resetPosition() {
  let myDiv = document.getElementById("detail");
  myDiv.scrollTop = 0;
}

This takes the id="detail" from the html container and then sets the scroll position to the top of this div rather than the entire body which was already at the top.

Jeremy
  • 1,038
  • 11
  • 34
  • (activate) seems to be a good hook. Instead of document.getElementById, can we use ViewChild ? – Nikhil Walvekar Mar 04 '20 at 06:26
  • Solution doesn't cover cases when there are a lot of images on the page, the images expands the scrollToTop doesn't take the page completely to beginning rather at some place in middle – Ravinder Payal Apr 30 '21 at 01:06