Open mtpultz opened 7 years ago
Building on @mtpultz answer, you can keep it little simpler if your page is always going to have md sidenav, and without relying on third party library which I assume is what .scroll()
is about.
this.router.events
.filter(event => event instanceof NavigationEnd)
.subscribe(() => {
document.querySelector('.mat-sidenav-content').scrollTop = 0;
});
I'm using the following in app.component.ts which preserves the scroll position when navigating back or forward. It is based off https://stackoverflow.com/a/44372167/684893
private lastPoppedUrl: string | undefined;
private yScrollStack: number[] = [];
private baseScrollOptions: ScrollToOptions = {
left: 0,
top: 0,
behavior: 'smooth',
};
constructor(private router: Router, private location: Location) {}
ngOnInit() {
this.location.subscribe((ev: PopStateEvent) => {
this.lastPoppedUrl = ev.url;
});
this.router.events.subscribe((ev: any) => {
const sidenavContentElement = document.querySelector(
'.mat-sidenav-content',
);
if (!sidenavContentElement) {
return;
}
if (ev instanceof NavigationStart) {
if (ev.url !== this.lastPoppedUrl) {
this.yScrollStack.push(sidenavContentElement.scrollTop);
}
} else if (ev instanceof NavigationEnd) {
if (ev.url === this.lastPoppedUrl) {
this.lastPoppedUrl = undefined;
sidenavContentElement.scroll({
...this.baseScrollOptions,
top: this.yScrollStack.pop(),
});
} else {
sidenavContentElement.scroll(this.baseScrollOptions);
}
}
});
}
Just chiming in here... this is fixed by https://github.com/angular/angular/pull/20030 and is being released in @angular/router@6.1.0
@jelbourn @andrewseguin @crisbeto this can be closed.
@damienwebdev I'm not sure that this can be closed. Based on https://github.com/angular/angular/issues/7791#issuecomment-428259641, there appear to be some incompatibilities or lack of documentation for how scrollPositionRestoration
should work with a mat-sidenav-container
.
Some of the issues related to Angular Material and the SideNav also effect Ionic and other component sets. They are discussed in https://github.com/angular/angular/issues/24547.
Disclaimer: I've post these somewhere else before, but as @Splaktar requested, here are some demos of the routing scrolling issue that I'm experiencing.
Here are two examples, the first working and the second not:
mat-sidenav-container
: https://stackblitz.com/edit/angular-material2-mat-sidenav-router-issuemat-sidenav-container
: https://stackblitz.com/edit/angular-material2-mat-sidenav-fullscreen-router-issueWhy that happens? Because when mat-sidenav-container
is used with its fullscreen
attribute, it gets its own scrolling container instead of relying on the default viewport overflow behavior.
A curious fact is that the router scrolling seems to work when you play with the browser history, but not when you click the links.
I'm experiencing this issue on a new project using "@angular/core": "~7.2.4"
and "@angular/material": "^7.3.2"
with MatSidenavModule
. I agree with the others that not having the scroll position behave as expected in a standard browser app/site (i.e. non single page app) is poor for usability and accessibility, and does not meet user expectations.
I'd like to emphasise that accessibility is a large component for my work and user base.
This issue, in my experience, has additionally made it more difficult than needed to recommend to stakeholder that an Angular/Material combo is a stable choice for a platform.
I've implemented a couple of suggestions from other comments here, linked issues, stackoverflow, and my own code but they all have some minor issue which doesn't make navigation and scroll position feel natural.
I see that in viewport_scroller.ts
there is a class BrowserViewportScroller
with a constructor constructor(private document: any, private window: any) {}
.
Looking for a solution here, could Material provide a way to specify what elements the BrowserViewportScroller
should accept as the main content scroll regions and inject that into the Angular app at load time?
That way the app would know which DOM element to treat as the main view region and listen to scroll events within that area instead of the default document
or window
which may not always be scrollable due to the implementation. Granted there can also be multiple side nav components so perhaps a .forRoot()
equivalent might help here.
Maybe there is already a way of providing an injection like this into Angular but I'm not familiar with how to do that or if that functionality exists. Either way, having this as part of Material might help to solve the issue or bring it closer to a solution.
I have an ugly solution for this. I think it's a hack.
<mat-sidenav-content #benji>
...
<router-outlet (activate)="onActivate($event)"></router-outlet>
...
</mat-sidenav-content>
then the onActivate($event) funciton scrolls to the top
@ViewChild('benji', {static: false}) el !: MatSidenavContent;
onActivate(e: any) {
if(this.el && this.el.getElementRef().nativeElement) {
this.el.getElementRef().nativeElement.scrollTop = 0;
}
}
I think it's ugly but it seems to work.
I miss the browser's native scrolling. :disappointed:
Any updates on this?
Kind of like @BenjaminHofstetter solution above, I found that if I use an obscure html element (in my case <cite>
and set it directly above the router-outlet, put it's styles to a negative top margin and position absolute, I was able to use
onActivate(e) { if (isPlatformBrowser) { document.querySelector('cite').scrollIntoView() } }
The negative margin makes the browser attempt to scroll past 0, but since it can't the mat-sidenav-container content stops at the top.
this.router.events .filter(event => event instanceof NavigationEnd) .subscribe(() => { document.querySelector('.mat-sidenav-content').scrollTop = 0; });
Hi, I worked without the filter; this.router.events .subscribe(() => { document.querySelector('.mat-sidenav-content').scrollTop = 0; });
I believe the folks who wrote the angular documentation have a workaround. Although it is rather overkill... But it really shows the principle of approaching this problem - even when anchor tags and hashes are involved.
https://github.com/angular/angular/tree/master/aio/src/app
The files to look for are
It's going to be tough trying to understand exactly what they have going on in there, and this won't even give you a complete idea. BUT, you will gain many pointers of how to "coax" angular into doing what you want to do...
fyi: Anchor Scrolling is also not working if you use fullscreen on <mat-sidenav-content>
: https://lachimi.com/angular/routing-navigation/extra-options/anchor-scrolling
it should be simply possible to define in the routerExtraOptions the scrollable element.
Bug, feature request, or proposal:
Feature Request
What is the expected behavior?
Using a common native API call to
window.scrollTo(0, 0)
or doing something similar on the document body, etc would allow for scrolling the page to the top after a route ends when using asidenav
, otherwise the next view starts at the same position in the page.What is the current behavior?
Routing between views within the sidenav content maintains the same scroll position assuming the content is the same length.
What are the steps to reproduce?
Add a sidenav and try to scroll to the top of the page anywhere at anytime. Eventually, I figure out this solution and stuck it in my
app.component
.What is the use-case or motivation for changing an existing behavior?
Routing between views within the sidenav should be able to start at the top of the page, which is a common use case. Also, scrolling a browser using the native API has been around forever so it's a bit confusing when you expect it or one of the many variations of scrolling to work and they don't.
Which versions of Angular, Material, OS, browsers are affected?
Angular 4.0.2 Angular Material 2.0.0-beta.3