Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changing route doesn't scroll to top in the new page #7791

Closed
netlander opened this issue Mar 28, 2016 · 103 comments
Closed

Changing route doesn't scroll to top in the new page #7791

netlander opened this issue Mar 28, 2016 · 103 comments

Comments

@netlander
Copy link

netlander commented Mar 28, 2016

When navigating from one route to another then scrolling down a bit and navigate back to the previous route, the page remains at the same scroll position.

Current behavior

Navigate from one view to another then scroll down a bit and navigate back to the previous view. the page remains at the same scroll position.

Expected/desired behavior

When navigating back to another route the page should be scrolled to the top.

@zoechi
Copy link
Contributor

zoechi commented Mar 28, 2016

dup of #6595 ?

@mgol
Copy link
Member

mgol commented Mar 30, 2016

dup of #6595 ?

I don't think so.

@zoechi
Copy link
Contributor

zoechi commented Mar 30, 2016

#6946 shows a workaround

@xak2000
Copy link

xak2000 commented May 10, 2016

Expected/desired behavior

When navigating back to another route the page should be scrolled to the top.

It isn't expected behaviour for me. Expected behaviour for me is:

When navigating back to another route the page should be scrolled to position at which it was before going forward. This is the behaviour currently implemented in all browsers when navigating between normal pages (not SPA). But I never seen this behaviour in any implementation of angular1 router.

It is very annoying when you scroll down some list, click on list item to see details page, scrolling down details, then click browser Back button and not see the list page in exactly the same state as it was before.

  • If it not scrolls at all: you just end up in random scroll position on the list page.
  • If it scrolls to the top: it's better, but you end up always at top of the list page, while in any "normal" site (not SPA) you end up at scroll position at which you were been before going to detail page.

The github, for example, implements this correctly: Open issues list and scroll down, then open any issue details, then click Back browser button. You will end up at exactly the same scroll position at which you were been before opening issue details page.

It will be very cool if angular2 router able to do it. It is really a killer feature I never seen in any other routers.

Edit
There is dedicated issue for this behaviour now #10929

@sulandr
Copy link

sulandr commented May 24, 2016

may be would be better ta have some attribute or param to change this default scroll behaviour?

@andyrue
Copy link

andyrue commented Jul 28, 2016

Is there a recommended method for scrolling to the top in RC.4? The suggested workaround in #6946 no longer works.

@cortopy
Copy link

cortopy commented Jul 29, 2016

@andyrue I don't think this would be recommended but I posted an update on #6946

@mhamel06
Copy link

mhamel06 commented Aug 5, 2016

In my case the window.scrollTo(0, 0) trick wouldn't work because I actually need to scroll to the top of an overflowing router-outlet. If you have a similar issue this is what I did as a workaround:

@Component({
  template: '<router-outlet (activate)="onActivate($event, outlet)" #outlet></router-outlet>,
})
export class MyParentComponent {
  onActivate(e, outlet){
    outlet.scrollTop = 0;
  }
}

@Ariix
Copy link

Ariix commented Aug 12, 2016

Any solution? None of the proposed solutions work.
This seems to be working for me-for how long, not sure:

ngAfterViewChecked() {
window.scrollTo(0, 0);
}

@cortopy
Copy link

cortopy commented Aug 14, 2016

@Ariix wouldn't that scroll after every time change detection runs?

@Ariix
Copy link

Ariix commented Aug 18, 2016

@cortopy Yes, yes it does. So still looking

@robinkedia
Copy link

Any update or milestone for this?

@naveedahmed1
Copy link
Contributor

Any update on this? Its annoying when you scroll down some list, click a list item to see details (which is on another route) you have to scroll up. If the next details page's length is small and the list page is very long and by chance you clicked the last item in the list. It would give an impression that there aren't any contents on details page (as the user has to scroll up to view contents).

@zoechi
Copy link
Contributor

zoechi commented Sep 10, 2016

@naveedahmed1 #6595 (comment)

@naveedahmed1
Copy link
Contributor

@zoechi I believe that comment is about URL with hash, in my case I don't' have any hash:

List page has a url:
/list

details page has a url:
/details/123

@zoechi
Copy link
Contributor

zoechi commented Sep 10, 2016

@naveedahmed1 you can still call scrollIntoView() or scrollTop=0

@robinkedia
Copy link

robinkedia commented Sep 19, 2016

@zoechi - Can you share the full code? i have parent child scenario where child routes are changing

@sime
Copy link

sime commented Sep 20, 2016

My work around involves the ElementRef class.

  constructor(
    private route: ActivatedRoute,
    private service: GuideService,
    private router: Router,
    private element: ElementRef
  ) {
    this.scrollUp = this.router.events.subscribe((path) => {
      element.nativeElement.scrollIntoView();
    });
  }

@naveedahmed1
Copy link
Contributor

How about this?

import { Component, OnInit} from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';

@Component({
    moduleId: module.id,
    selector: 'app',
    templateUrl: './app.component.html'
})

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

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

            var scrollToTop = window.setInterval(function () {
                var pos = window.pageYOffset;
                if (pos > 0) {
                    window.scrollTo(0, pos - 20); // how far to scroll on each step
                } else {
                    window.clearInterval(scrollToTop);
                }
            }, 16); // how fast to scroll (this equals roughly 60 fps)
        });
    }
}

@luxalpa
Copy link

luxalpa commented Oct 8, 2016

    router.events.filter(event => event instanceof NavigationEnd).subscribe(event => {
        window.scroll(0, 0);
    });

This seems to remember the scroll position (in Chrome and Firefox but not IE), but I have no idea why.

@daco
Copy link

daco commented Oct 14, 2016

@SmaugTheGreat Yes, that does work! But how is that even possible that it remembers the last scroll position?

@naveedahmed1
Copy link
Contributor

Its strange, when using @SmaugTheGreat solution i.e. window.scroll(0, 0); it remembers scroll position.
But when using it window.scroll in window.setInterval, it doesn't remember scroll position.

@fxck
Copy link

fxck commented Dec 15, 2016

why doesn't NavigationEnd contain params, query params etc of the route? only string?

@shanehickeylk
Copy link

shanehickeylk commented Dec 20, 2016

A mixture of Smaugs solution above and Guilherme Meireles on stackoverflow does the trick for me. Also unsubscribing in ngOnDestroy to avoid leakage.

import { Component } from '@angular/core'; 
import { Router, NavigationEnd } from '@angular/router'; 
import { Subscription } from 'rxjs/Rx';

@Component({
    moduleId: module.id, 
    selector: ‘my-app’,
    template: `<router-outlet></router-outlet>`
})

export class AppComponent {
    routerSubscription: Subscription;

    constructor(private router: Router) {}

    ngOnInit() {
        this.routerSubscription = this.router.events
            .filter(event => event instanceof NavigationEnd)
            .subscribe(event => {
                document.body.scrollTop = 0;
            });
    }

    ngOnDestroy() {
        this.routerSubscription.unsubscribe();
    }
}

@gowthamrodda
Copy link

gowthamrodda commented Dec 22, 2016

use 'ng2-page-scroll' module. As a quick response, i am posting one of my component. This one worked for me.

import {Component, OnInit, Inject } from '@angular/core';
import {Router, Route, NavigationEnd} from '@angular/router';
import { AppSharedService} from '../app.service';
import { DOCUMENT } from '@angular/platform-browser';
import { Subscription } from 'rxjs/Rx';
import { PageScrollService, PageScrollInstance } from 'ng2-page-scroll';

@component({
selector: 'app-resources',
templateUrl: './resources.component.html',
styleUrls: ['./resources.component.scss']
})
export class ResourcesComponent {

routerSubscription: Subscription;

constructor(private router: Router, private SharedService: AppSharedService, private pageScrollService: PageScrollService, @Inject(DOCUMENT) private document: any,) {
let pageScrollInstance: PageScrollInstance = PageScrollInstance.simpleInstance(this.document, '#app-give');
this.pageScrollService.start(pageScrollInstance);

}

}

@spock123
Copy link

spock123 commented Jan 3, 2017

@shanehickeylk
I believe you only need to unsubscribe because you specifically save the subscription into a variable.

If you just called router.events..... .subscribe() directly, there is no subscription to unsubscribe.

Update: yes you do :) sorry guys

@shanehickeylk
Copy link

@spock123
I believe that the subscription is still "alive" even when it is not assigned to a variable. This is how angular knows an event has been triggered. The reason for assigning it to a variable is so that it can be accessed later and unsubscribed.

Here is a good article by Brian Love, where he goes into some additional detail on the topic:
http://brianflove.com/2016/12/11/anguar-2-unsubscribe-observables/

@peterpeterparker
Copy link

@mkhan004 interesting but may I ask, behavior is not browser native right (https://www.w3schools.com/jsref/met_win_scrollto.asp)?

therefore are you using a special lib/polyfill which override window.scrollTo or something else?

@mgol
Copy link
Member

mgol commented Jun 7, 2018

@peterpeterparker It is native browser behavior. Word of advice - don't use W3Schools, it is known for imprecise or just plain wrong information. It's better to use MDN; a relevant page here: https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollTo

@peterpeterparker
Copy link

@mgol thx 👍

@MartinMa
Copy link
Contributor

MartinMa commented Jun 7, 2018

Worth mentioning: behavior: 'smooth' is not supported by IE or Edge.

Current versions of Chrome and Firefox seem to work though.

@mgol
Copy link
Member

mgol commented Jun 7, 2018

@MartinMa That's a good point; the relevant Can I Use page is https://caniuse.com/#feat=css-scroll-behavior

Note that even just omitting behavior: 'smooth' will still break IE/Edge/Safari as they don't support the object syntax to scrollTo, just the positional one: scrollTo(x, y). To support both of them with Chrome/Firefox getting smooth scroll and others non-smooth scroll, you can use the following logic (with the desired x/y values):

if ('scrollBehavior' in document.documentElement.style) {
  window.scrollTo({
    behavior: 'smooth',
    left: x,
    top: y,
  });
} else {
  window.scrollTo(x, y);
}

@javid-abd
Copy link

so what's the best option to fix this issue?

jasonaden pushed a commit to vsavkin/angular that referenced this issue Jun 7, 2018
For documentation, see `RouterModule.scrollPositionRestoration`

Fixes angular#13636 angular#10929 angular#7791 angular#6595
mhevery pushed a commit that referenced this issue Jun 8, 2018
For documentation, see `RouterModule.scrollPositionRestoration`

Fixes #13636 #10929 #7791 #6595

PR Close #20030
@trotyl
Copy link
Contributor

trotyl commented Jun 9, 2018

Closed by #20030

@jasonaden
Copy link
Contributor

Closing based on being fixed as above.

@Johnyoat
Copy link

use window.scrollTo(0,0); in ngOnit. So it should look like this
ngOnInit() { window.scrollTo(0,0); }

@javid-abd
Copy link

just have a look at Angular 6.1. It was fixed, and probably in a future release, it will be default

@rajkumargadhavi
Copy link

here is the best solution!!!

import { Component } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';

@component({
selector: 'sv-app',
template: ''
})
export class AppComponent {

constructor(private router: Router) {

router.events
  .filter(event => event instanceof NavigationEnd)
  .subscribe((event: NavigationEnd) => {
    window.scroll(0, 0);
  });

}
}

@JamieFarrelly
Copy link

A workaround is to put this in application.run() - at least in an old version of Angular. Might help someone!

var interval = setInterval(function() {
     if (document.readyState === 'complete') {
       $window.scrollTo(0, 0);
       clearInterval(interval);
     }
}, 10);

@Toub
Copy link

Toub commented Sep 13, 2018

@JamieFarrelly an official solution has been released in angular 6.1

router: implement scrolling restoration service (#20030) (49c5234), closes #13636 #10929 #7791 #6595

@see documentation:

[edit] so in your case:

@NgModule({
  imports: [RouterModule.forRoot(routes, {
    scrollPositionRestoration: 'top'
  })],
  exports: [RouterModule]
})

@odahcam
Copy link
Contributor

odahcam commented Oct 9, 2018

This won't work with Angular Material <mat-sidenav-container fullscreen>, I think that's because the scrolling container is now <mat-sidenav-content> and not the document body. Is there a way to fix this?

@PixelShoot
Copy link

PixelShoot commented Oct 10, 2018

@odahcam I'm using MatDrawerContainer and it works form with ViewChild

@ViewChild('scroll') myScrollContainer:MatDrawerContainer; 
this.myScrollContainer.scrollable.getElementRef().nativeElement.scrollTop= 0;

@odahcam
Copy link
Contributor

odahcam commented Oct 10, 2018

Yeah, I'm able to scroll the container, but how to make the router automatically scroll it for me? (like it would with viewport)

@PixelShoot
Copy link

PixelShoot commented Oct 10, 2018

@odahcam

  <router-outlet (activate)="onActivate($event)"></router-outlet>
onActivate(event){
    this.myScrollContainer.scrollable.getElementRef().nativeElement.scrollTop= 0;
}

or I misunderstood you?

@odahcam
Copy link
Contributor

odahcam commented Oct 10, 2018

Actually I'm using something like that, but I think the router should have some scrollable container configuration, beucase it tries to scroll by the viewport when you enable the scrollPositionRestoration: 'top' option and it won't work.

Your code does the job as mine does too, but both of us are unable to take profit from the native router functionality that does the scrolling job for us. To do that, we would need to tell the router to use our custom scrollable container instead of the viewport.

I noticed that the Router makes use of a guy called ViewportScroller and, as the docs says, you should inject that guy in your module or component if you wan't to customize the scroll behavior:

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]);
     }
   });
 }
}

That makes me think that the only way to integrate something like a fullscreen cdkContainer to the router scroll restoration would be implement my own ViewportScroller and alter the injection token that Angular uses for the router to inject my scroller instead of the default.

Unfortunately the docs for this matter is a little bit inexistent, so things are even more difficult.

@vishwackh
Copy link

Angular 6.1 and later:

RouterModule.forRoot(routes, {scrollPositionRestoration: 'enabled'})

@Splaktar
Copy link
Member

@odahcam I've linked your comments to a related Angular Material issue: angular/components#4280. It would be great if you could provide a Stackblitz example and some additional information there.

@odahcam
Copy link
Contributor

odahcam commented Oct 29, 2018

@Splaktar all right, posted.

@zhuhang-jasper
Copy link

zhuhang-jasper commented Dec 26, 2018

A mixture of Smaugs solution above and Guilherme Meireles on stackoverflow does the trick for me. Also unsubscribing in ngOnDestroy to avoid leakage.

import { Component } from '@angular/core'; 
import { Router, NavigationEnd } from '@angular/router'; 
import { Subscription } from 'rxjs/Rx';

@Component({
    moduleId: module.id, 
    selector: ‘my-app’,
    template: `<router-outlet></router-outlet>`
})

export class AppComponent {
    routerSubscription: Subscription;

    constructor(private router: Router) {}

    ngOnInit() {
        this.routerSubscription = this.router.events
            .filter(event => event instanceof NavigationEnd)
            .subscribe(event => {
                document.body.scrollTop = 0;
            });
    }

    ngOnDestroy() {
        this.routerSubscription.unsubscribe();
    }
}

For those who cannot use filter(), map() etc operators on Observable add this:

import { filter, map } from 'rxjs/operators';

@Baathus
Copy link

Baathus commented Apr 15, 2019

Angular 6.1 and later:

RouterModule.forRoot(routes, {scrollPositionRestoration: 'enabled'})

To others looking for a simple and clean solution for keeping the scroll position on back navigation and scroll to top on forward navigations, this is the solution. Will be default Angular behaviour in the future (described by the angular specifications: https://angular.io/api/router/ExtraOptions)

@maximelafarie
Copy link

maximelafarie commented Jul 11, 2019

Angular 6.1 and later:
RouterModule.forRoot(routes, {scrollPositionRestoration: 'enabled'})

To others looking for a simple and clean solution for keeping the scroll position on back navigation and scroll to top on forward navigations, this is the solution. Will be default Angular behaviour in the future (described by the angular specifications: https://angular.io/api/router/ExtraOptions)

I would like to add a half thumb up! 😅

If you have the following ExtraOptions:

// ...
const routerOptions: ExtraOptions = {
  useHash: false,
  anchorScrolling: 'enabled',
  scrollPositionRestoration: 'enabled'
};
// ...

in order to enable anchor links, it'll always go on top, even if the anchor is at the bottom of the page.

But if you're not using anchor navigation, it's an awesome fix! 😉

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 15, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests