question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

scrollPositionRestoration has several problems

See original GitHub issue

I’m submitting a…


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Performance issue
[x] Feature request
[x] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
[ ] Other... Please describe:

Current behavior

I started experimenting with the new scrollPositionRestoration feature in the RouterModule extra options. I expected scroll restoration to work by default when setting the property to ‘enabled’, but it doesn’t. And the documentation has issues, too.

Expected behavior

The documentation says:

‘enabled’–set the scroll position to the stored position. This option will be the default in the future.

So I naïvely thought that setting the flag to ‘enabled’ would be sufficient to restore the scroll position. But it isn’t.

Indeed, the scroll event is fired, and the scroll position is restored, before the ngAfterViewInit hook of the activated component has been called. So the view of the component is not ready yet when the router tries to restore the scroll position (i.e. there is no way to scroll to the end of a long list, because the list isn’t there yet).

And even if it was restored after the view is ready, that would only work if the activated component used a resolved guard to load the data.

So, the documentation should, IMHO, at least indicate that restoring the scroll position always requires to

  • explicitly intercept the Scroll event, and scroll imperatively after a delay. This can be done in a single place, but I don’t see how to do that in a reliable way, since there is no way to know if the delay is sufficient for the data to have been loaded (but it would at least work if resolve guards are used consistently), or
  • explicitly intercept the Scroll event in each routed component, and imperatively scroll when the data has been loaded and the view has been rendered. This is not a trivial task.

I read the remaining of the documentation, which has examples about doing this kind of stuff (although it doesn’t really say that they’re required). But those examples are all incorrect.

Here’s the first example:

    class AppModule {
     constructor(router: Router, viewportScroller: ViewportScroller, store: Store<AppState>) {
       router.events.pipe(filter(e => e instanceof Scroll), switchMap(e => {
         return store.pipe(first(), timeout(200), map(() => e));
       }).subscribe(e => {
         if (e.position) {
           viewportScroller.scrollToPosition(e.position);
         } else if (e.anchor) {
           viewportScroller.scrollToAnchor(e.anchor);
         } else {
           viewportScroller.scrollToPosition([0, 0]);
         }
       });
     }
    }

This example uses a Store service, which is not part of Angular (I guess it’s part of ngrx). So that makes it hard to understand and adapt for those who don’t use ngrx.

Besides, it doesn’t compile, because a closing parenthesis is missing, and because e is of type Event, and not of type Scroll, and thus has no position property.

The second example is the following:

    class ListComponent {
      list: any[];
      constructor(router: Router, viewportScroller: ViewportScroller, fetcher: ListFetcher) {
        const scrollEvents = router.events.filter(e => e instanceof Scroll);
        listFetcher.fetch().pipe(withLatestFrom(scrollEvents)).subscribe(([list, e]) => {
          this.list = list;
          if (e.position) {
            viewportScroller.scrollToPosition(e.position);
          } else {
            viewportScroller.scrollToPosition([0, 0]);
          }
        });
      }
    }

It doesn’t compile because it still uses an old, non-pipeable operator, and because, once again, e is of type Event, not Scroll.

But even after fixing the compilation errors, it doesn’t work because the view hasn’t been updated with the new list yet when viewportScroller.scrollToPosition(e.position); is called.

So the code would have to be changed to the following in order to compile and work as expected

    class ListComponent {
      list: any[];
      constructor(router: Router, viewportScroller: ViewportScroller, fetcher: ListFetcher) {
        const scrollEvents = router.events.filter(e => e instanceof Scroll);
        listFetcher.fetch().pipe(withLatestFrom(scrollEvents)).subscribe(([list, e]) => {
          this.races = list;
          const scrollEvent = e as Scroll;
          of(scrollEvent).pipe(delay(1)).subscribe(s => {
            if (s.position) {
              viewportScroller.scrollToPosition(s.position);
            } else {
              viewportScroller.scrollToPosition([0, 0]);
            }
          });
        });
      }
    }

I think that none of these solutions is really simple enough, though. Here are two ideas that could maybe make things easier:

  • only fire the Scroll event and try to restore the position after the ngAfterViewInit hook has been called. This should at least make things work when a resolve guard is used to load the list. Or when the list is available immediately.
  • for the other cases, allow to inject a service that the component could call when the list has been loaded. It would be up to this service to get the last scroll position or anchor, to wait until the view has been rendered, and then to restore the scroll position. It would ignore all but the first call after the component has been activated.

Minimal reproduction of the problem with instructions

Here’s a repo illustrating the various issues and solutions presented above: https://github.com/jnizet/scrollbug. It’s a standard angular-cli project. I can’t run it in stackblitz unfortunately (probably because Stackblitz doesn’t support the beta release of Angular).

What is the motivation / use case for changing the behavior?

First, the documentation should be fixed and made clearer

  1. it should not use ngrx
  2. it should contain examples that compile, and run as expected
  3. it should make it clear than simply setting the flag to ‘enabled’ is not sufficient to enable scroll restoration

Second, it should be way easier to make that feature work. See ideas above.

Environment


Angular version: 6.1.0-beta.1


Browser:
- [x] Chrome (desktop) version 67.0.3396.87
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [x] Firefox version 60.0.2 
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:289
  • Comments:114 (22 by maintainers)

github_iconTop GitHub Comments

46reactions
DrDagercommented, May 19, 2020

I have resolved this issue by implementing custom scroll restoration behavior.

The reason of this behavior is that page didn’t already render and scrollToPosition no have effect.

There are not a good hack with timeout but it works.

export class AppModule {
  constructor(router: Router, viewportScroller: ViewportScroller) {
    router.events.pipe(
      filter((e): e is Scroll => e instanceof Scroll)
    ).subscribe(e => {
      if (e.position) {
        // backward navigation
        setTimeout(() => {viewportScroller.scrollToPosition(e.position); }, 0);
      } else if (e.anchor) {
        // anchor navigation
        setTimeout(() => {viewportScroller.scrollToAnchor(e.anchor); }, 0);
      } else {
        // forward navigation
        setTimeout(() => {viewportScroller.scrollToPosition([0, 0]); }, 0);
      }
    });
  }
}
42reactions
damienwebdevcommented, Aug 26, 2019

Just leaving this here for all future visitors, as I did some debugging to determine why this wasn’t working. For anyone who sees this in the future. If you have the following code in your stylesheet…

html,body {height: 100%}

This expected functionality will appear to be a bug for you.

Removing that style has a decent potential to fix this issue and make this PR do what was intended. Granted, removing that style may make a bunch of other things break, but that’s a different problem.

Ninja edit: It also appears that this seems to intermittently work with https://github.com/angular/material2

Working Version (My own personal anecdote): I have a working app with material (which uses mat-sidenav) and all window.scroll variants work as expected.

Non-working version (as reported by @crisbeto ): https://github.com/angular/material2/issues/11552

Read more comments on GitHub >

github_iconTop Results From Across the Web

Angular - Unable to use "scrollPositionRestoration"
I have demonstrated that scrollPositionRestoration can work and you haven't given any details about when/where it doesn't work. Hope the ...
Read more >
Reactive Scroll Position Restoration with RxJS - Medium
A common issue is that scroll position isn't as predictable inside of an SPA — scrolling down a page with lots of content...
Read more >
Angular Tips: Understand Angular scroll position and ...
This event fires whenever a new route has been activated. ... In practice, the scroll position restoration feature of Angular doesn't do ...
Read more >
Scroll to top issue in Angular apps - Aditya Tyagi
The flag configures if the scroll position needs to be restored when navigating back. It has 3 values: disabled– (Default) Does nothing. The ......
Read more >
disabling scrollPositionRestoration for certain routes or modules.
I have an Angular app with quite a few lazy-loaded modules. I'm using `scrollPositionRestoration: 'top'` on my AppRoutingModule and ...
Read more >

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found