We found a bug. We had top content and a tab group below this content. And then we scrolled a little bit to bottom. We switched between some tabs. Now, page scrolled to top automatically.
It only occurs when we have top content on this page.
Page should keep scroll position when switching some tabs
Page scrolls to top when switching some tabs
-Step 1: Scroll a little bit to bottom on page
-Step 2: Click on Tab 3
The page will scroll to top.
Demo here: https://stackblitz.com/edit/angular-wjtrxy
A bug
Angular 5.0.3 - Material 5.0.2 - Window 10 - Typescript 2.4.2
Thanks a lot!
I have the same Issue.
And I noticed that the height of the "top content" seems to have an influence on this problem.
If you change the height to 900px for example, it doesn't scroll to top anymore.
Confirmed. Have the same issue.
馃憤 same issue
same issue
Well the reason is when you change the tab, the next tab not have that much height of its inner contents so the parent height also decrease. thats why the its scroll to top. try the parent element height fixed like 1000px and then change tab, it will not scroll up. 馃憤
https://stackblitz.com/edit/angular-wjtrxy-g4ksm6
Add a min-height="800px" to parent elemnt of mat-tab-group. :)
Thanks @umimehar. That's a good workaround.
Seriously, why is there no clean solution to this existing problem starting from 2015!
.fill-available {
min-height: min-height: stretch;
}
With this class called in the main container of the page worked for me, only in chrome not in mozilla.
Better -webkit-fill-available (tested on google chrome). Here is an example https://stackblitz.com/edit/angular-wjtrxy-x9shma
None of the suggested solutions completely fix the problem for me, I still end up with some weird scrolling behaviors with a pixel min-height
, stretch, fill-available...
edit: I modified @luchoman08 's stackblitz to reflect the problem:
https://stackblitz.com/edit/angular-wjtrxy-utgzn1
I removed some content from tab1 so now if you go to tab4 for example, scroll down a bit, then click tab1, the scrolling bug is still there, switch between those 2 for extra sea sickness effects.
still no solution for this? page shouldn't go up when click on the tab :/
Still a problem with Angular 6.1 and Angular Material 6.4.
min-height
property don't work with MS Edge, still jumps to the top.
Still a problem with angular 7.1.4 and material 7.2
Same issue here.
another hack approach, is to set min-height and padding/margin bottom for short content and to keep a footer in place.
min-height: 450px;
padding-bottom: 200px;
margin-bottom: -200px;
"min-height" solution did not work for me. It works, but i had to set min-height to meaningless value. I wrote directive that works for me. You may have to change document.documentElement to your scrolling element. It is not extra clean solution, but it works.
```import {AfterViewInit, Directive} from '@angular/core';
import {MatTabChangeEvent, MatTabGroup} from '@angular/material';
@Directive({
selector: '[mat-tab-scroll-fix]'
})
export class MatTabScrollFixDirective implements AfterViewInit {
constructor(private matTabGroup: MatTabGroup) {
}
private scrollPosition: number;
private tabChanging: boolean;
ngAfterViewInit(): void {
const scrollHandler = (event) => {
if (this.tabChanging) {
document.documentElement.scrollTop = this.scrollPosition;
}
this.scrollPosition = document.documentElement.scrollTop;
};
window.addEventListener('scroll', scrollHandler);
this.matTabGroup.selectedTabChange.subscribe((tabChangeEvent: MatTabChangeEvent) => {
this.tabChanging = false;
document.documentElement.scrollTop = this.scrollPosition;
});
this.matTabGroup.selectedIndexChange.subscribe((index: number) => {
this.tabChanging = true;
});
}
}
```
Found same issue.
Still have this problem. Angular 8
I might have a solution for this. Its a crude solution, but is seems to be working, at least for me it does :) So let me explain:
You need a parent component, which has the entire mat-tab-group code, and child components, for each individual mat-tab body (I suppose you can do without parent/child as well).
Parent component HTML:
<mat-tab-group (selectedTabChange)="forceScrollPosition($event)">
<mat-tab>
<app-tab-child-0
[scrollPosition]="scrollChild0"
[canUpdateScroll]="updateChild0Scroll"
(scrollChanged)="scrollChanged($event)">
</app-tab-child-0>
</mat-tab>
<mat-tab>
<app-tab-child-1
[scrollPosition]="scrollChild1"
[canUpdateScroll]="updateChild1Scroll"
(scrollChanged)="scrollChanged($event)">
</app-tab-child-1>
</mat-tab>
</mat-tab-group>
Parent component TS:
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-tab-parent',
templateUrl: './app-tab-parent.html',
styleUrls: ['./app-tab-parent.scss']
})
export class ParentComponent implements OnInit {
// Hold scroll positions for each child (can be also array of values, it doesnt matter)
scrollChild0: number = 0;
scrollChild1: number = 0;
// Permission for child component to emit scroll change
updateChild0Scroll: boolean = true; //The FIRST tab displayed should have this attribute set to ,,true"
updateChild1Scroll: boolean = false;
// There are several ways to trigger method in child, I prefer this one
@ViewChild(TabChild0Component) private tabChild0Component: TabChild0Component;
@ViewChild(TabChild1Component) private tabChild0Component: TabChild1Component;
//Here set new scroll position for specific child tab
public scrollChanged(event: any) {
//example
if(event.index === 0) {
this.scrollChild0 = event.value;
}
else if ...... {
....
}
}
// When selected tab is changed, revalidate permissions and force child to load latest scroll
public forceScrollPosition(event: any) {
//example
this.updateChild0Scroll= event.index === 0;
this.updateChild1Scroll= event.index === 1;
....
// now you need to identify selected tab and force child component to load last saved scroll
if(event.index === 0) {
this.tabChild0Component.loadScroll();
}
else if ...... {
....
}
}
}
Child component TS:
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-tab-child-0',
templateUrl: './app-tab-child-0.html',
styleUrls: ['./app-tab-child-0.scss']
})
export class TabChild0Component implements OnInit {
@Input() scrollPosition: number;
@Input() canUpdateScroll: boolean;
@Output() scrollChanged = new EventEmitter();
// This path targets mat tab body. the ,,#mat-tab-content-0-0" needs to be changes, depending on
// child index position in the tabs (#mat-tab-content-0-0, #mat-tab-content-0-1, ...)
private readonly elementPath = "#mat-tab-content-0-0 .mat-tab-body-content.ng-trigger.ng-trigger-translateTab";
ngOnInit() {
let element = document.querySelector(this.elementPath);
let base = this;
if(element) {
element.addEventListener('scroll', function() {
// Check if this child is currently focused tab, otherwise it emits 0 by default and defeats the whole
// purpose
if(base.canUpdateScroll) {
base.scrollChanged.emit(document.querySelector(base.elementPath).scrollTop);
}
});
}
}
/** Load last known scroll position */
public loadScroll() {
let element = document.querySelector(this.elementPath);
if(element) {
document.querySelector(this.elementPath).scrollTop = this.scrollPosition;
}
}
}
Again, sorry for the crude code, but I hope it can give you at least some basic idea of my solution. If it will be helpful to anyone, well, I am glad I could help :)
https://stackblitz.com/edit/angular-wjtrxy-g4ksm6
Add a min-height="800px" to parent elemnt of mat-tab-group. :)
Thank you, I solved scroll to top of page issue using this code 馃憤
Especially bad with variable tab content as min-height
and padding
hacks are not working.
It would be great to get an update on this.
I was able to use min-height: 100% on the parent with no other styling.
Still have this problem. Angular material 8.2.1
Still a problem in 2020.
less min-height: 500px or 400px works too...
This is super-frustrating, and min-height
is absolutely a hack (more power to you if it works for your situation though.)
min-height
does exactly what you expect here, which is a problem for both static and dynamic tab contents. Say I have three tabs, A, B, and C, with "natural" (fits content, looks good, i.e. what the dev wants) heights of 600px, 800px, and 200px, respectively. Say I also have content above the tab group such that there's about 220px left "before the fold" (bottom of viewport) on a desktop browser. A min-height: 200px
will respect the min height of my component, but switching between tabs A and B while viewing them in full will always bounce to the top. Increasing min-height
any more will start adding extra whitespace to tab C, which could be perfectly acceptable, or could look like trash.
No one adequately explained what's going on, in my opinion, so here's an illustrative demo: https://stackblitz.com/edit/angular-material-bouncing-tabs?file=app%2Fbouncing-tabs-demo.html
There's an object with property, matTabsAnimations.translateTab
, that forces a min height of 1px for some issues. If that instead chose the height of the previous tab, I think that would solve things. That would eliminate this hidden third size that causes the jump.
For a slightly better hack, you can target .mat-tab-body-content.ng-trigger-translateTab.ng-animate-queued
instead of the containing element. You still have to pick a third size that will potentially cause unexpected jumps, but this doesn't impact the steady-state layout at least. If you give it a fat red border, slow the animation way down, and throttle the CPU down (via the Chrome debugger,) you still won't see it unless you're on that breakpoint.
Still an issue with Angular 9 & Angular Material 9.1.0.
Seriously.
Scrolls to top after route change.
1) Have a list of brands.
2)Scroll to the middle . Select any brand. Navigates to another route with its description.
3)Click on Back Button.
4)Redirects to the list but scrolls to top.
Still a problem in Angular 7, Material.
Required behaviour: Restore original scroll position.
Any solution?
Made a small directive that retains the scroll position when you change tabs.
import { Directive, ElementRef, OnDestroy } from '@angular/core'
import { fromEvent, Subscription } from 'rxjs'
@Directive({
selector: '[xxxScrollRetainer]'
})
export class ScrollRetainerDirective implements OnDestroy {
private changes: MutationObserver
private lastScrollPosition = 0
private subscription: Subscription
constructor(private elementRef: ElementRef) {
this.changes = new MutationObserver(() => this.rollbackScrollPosition())
this.changes.observe(this.elementRef.nativeElement, { childList: true, subtree: true })
this.subscription = fromEvent(window, 'scroll').subscribe(() => {
this.lastScrollPosition = window.pageYOffset
})
}
private rollbackScrollPosition() {
window.scrollTo(window.pageXOffset, this.lastScrollPosition)
}
ngOnDestroy(): void {
this.subscription.unsubscribe()
this.changes.disconnect()
}
}
The issue is unrelated to UI tab modules, I can confirmed because the same scrolling happened with ngbTab. The issue, for me at least, is storing tab state in the query params.
You can implement custom scroll restoration behavior by adapting the enabled behavior as in the following example.
https://angular.io/api/router/ExtraOptions#scrollPositionRestoration
Setting scrollPositionRestoration: 'enabled',
solves the issue, but screws up expected behavior in my app everywhere else. Use the custom check option the link above mentions to hack your edge case together.
For context, the min-height
does not solve it for me on Angular 9.
The same issue :(
The min height is depend the size of the window no always is needed 800px o 500px etc..
I do that:
Then:
The problem is the little placeholder element before sliding in the next tab. It has a min-height
of 1px, and even though it's in and out (and the next tab in the DOM to the side) before you can see it, the scroll change still happens.
If it instead latched the height of the outgoing tab, behavior would be as most people expect.
@jneuhaus20 I dosen't see that, can you send a screenshot ?
@britvik works for me. Thanks.
@britvik Which component do you attach your directive to? I attached it to mat-tab-group
that didn't work. Infact, the page didn't load.
@DavidTheProgrammer You can attach it to any element (e.g. div) that contains the tab group.
why is there no definite solution? This issue started from 2 years ago, not closed until now
@asyahril Hey c'mon get off their back, this is open source. What do you expect from a small company with limited resources like Google? I'm just happy the issue hasn't been automatically locked yet.
I'm also having this issue. I've tried @britvik's Directive (doesn't fix for me... fromEvent doesn't appear to fire), I've also tried min-height on containing div. Works in Chrome & Edge but not Firefox.
I've made this directive based on @ludarous answer, to make it work with Angular CDK scrolling. It work on my side, but not the smoothest option.
import { AfterViewInit, Directive, OnInit, OnDestroy } from '@angular/core';
import { MatTabGroup, MatTabChangeEvent } from '@angular/material/tabs';
import { ScrollDispatcher, CdkScrollable } from '@angular/cdk/scrolling';
import { Subscription } from 'rxjs';
@Directive({
selector: '[ScrollRetainer]'
})
export class ScrollRetainerDirective implements AfterViewInit, OnInit, OnDestroy {
constructor(private matTabGroup: MatTabGroup, private scrollDispatcher: ScrollDispatcher) {}
private container: CdkScrollable;
private scrollSub: Subscription;
private tabSub: Subscription;
ngOnInit(): void {
this.scrollSub = this.scrollDispatcher.scrolled().subscribe(
(el: CdkScrollable) => {
this.container = el;
});
}
ngAfterViewInit(): void {
this.tabSub = this.matTabGroup.selectedTabChange.subscribe((tabChangeEvent: MatTabChangeEvent) => {
this.container.scrollTo({
top: this.container.measureScrollOffset('top'),
left: 0,
behavior: 'auto'
});
});
}
ngOnDestroy(): void {
this.scrollSub.unsubscribe();
this.tabSub.unsubscribe();
}
}
after tweak the min-height
solution a few times, I realised the number is not totally random
if you set min-height
to any number, it will prevent any subsequent tab clicks but not the first one after page load/refresh, in fact it'll jump to window top and then scroll to tab top, that's even worse.
if you set min-height
to a number larger than the distance from top of the page to top of the nav tab, then it will prevent jumping for all tab clicks including the first click, that's why in a few previous examples in this thread, setting a min-height: 800px
fixes it. But it doesn't have to be 800, it just needs to be larger than the distance mentioned above.
So if you have a project with predicted layout, setting min-height might be the quickest hacky way to fix this.
This issue happening on my angular 9 application, but in different scenario.
I have modal pop-up, when i close the modal, page automatically scrolls up in internet explorer.
previously with angular 8 and material 7.3.3 I was able to fix this issue by adding window.scroll() event after modal event.
but after updating angular 9 and material 9. window.scroll() is not working anymore.
I removed window.scroll() event and in chrome and other browsers scroll behaviour working as I expected.. but not in internet explorer, so I am not sure if it is related to window.scroll() compability with IE or is material breaking something.
anyone figured out how to solve this issue?
Still happens in 9.1.12! Please fix this. It was driving me nuts till I used the work around, but work arounds are difficult to remember. my html now has bug comments in it. ugg
And another month goes by... if only google had developers working in house. They do? You don't say...
We had the same problem and fixed it with this workaround:
@Directive({
// tslint:disable-next-line: directive-selector
selector: 'mat-tab-group[scrollFix]'
})
export class PsMatTabGroupScrollFixDirective implements AfterViewInit, OnDestroy {
private matTabGroupEl: HTMLElement = this.el.nativeElement;
private animationSub = Subscription.EMPTY;
constructor(private matTabGroup: MatTabGroup, private el: ElementRef) {
}
public ngAfterViewInit(): void {
const orig = this.matTabGroup._handleClick.bind(this.matTabGroup);
this.matTabGroup._handleClick = (tab, tabHeader, index) => {
if (!tab.disabled) {
this.matTabGroupEl.style.minHeight = this.matTabGroupEl.clientHeight + 'px';
}
return orig(tab, tabHeader, index);
}
this.animationSub = this.matTabGroup.animationDone.subscribe(() => {
this.matTabGroupEl.style.minHeight = 'unset';
});
}
public ngOnDestroy() {
this.animationSub.unsubscribe();
}
}
Unfortunatelly it overrides the _handleClick method, which is not in the public api. So it can break at any update, but at least it works for now.
Valid arguments, but let's give the Angular devs some more time and not over-react.
Understand this is open source. Let's try to view things from their point of view.
Explore other options in the meantime.
Here's an updated demo that reproduces this issue with 11.0.3.
And another demo that demonstrates that this behavior still exists with the MDC-based tabs in @angular/material-experimental
.
Same happening to me, for now I'm solving this using tips provided above, but proper fix would be awesome.
Most helpful comment
https://stackblitz.com/edit/angular-wjtrxy-g4ksm6
Add a min-height="800px" to parent elemnt of mat-tab-group. :)