Skip to content

Commit 301f365

Browse files
committed
fix(tabs): preserve scroll position when switching between tabs
Preserves the scroll position when switching between tabs. Previously it was being reset to 0, because we detach and re-attach the content. Fixes #6722.
1 parent 1b6b270 commit 301f365

File tree

2 files changed

+47
-4
lines changed

2 files changed

+47
-4
lines changed

src/lib/tabs/tab-body.ts

+12-4
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,20 @@ export class MdTabBody implements OnInit, AfterViewChecked {
9191
/** The portal host inside of this container into which the tab body content will be loaded. */
9292
@ViewChild(PortalHostDirective) _portalHost: PortalHostDirective;
9393

94+
/** Element wrapping the tab's content. */
95+
@ViewChild('content') _contentElement: ElementRef;
96+
9497
/** Event emitted when the tab begins to animate towards the center as the active tab. */
9598
@Output() onCentering: EventEmitter<number> = new EventEmitter<number>();
9699

97100
/** Event emitted when the tab completes its animation towards the center. */
98101
@Output() onCentered: EventEmitter<void> = new EventEmitter<void>(true);
99102

100103
/** The tab body content to display. */
101-
@Input('content') _content: TemplatePortal<any>;
104+
@Input('content') _contentPortal: TemplatePortal<any>;
105+
106+
/** Scroll position of the tab before the user switched away. */
107+
private _lastScrollPosition = 0;
102108

103109
/** The shifted index position of the tab body, where zero represents the active center tab. */
104110
_position: MdTabBodyPositionState;
@@ -146,7 +152,8 @@ export class MdTabBody implements OnInit, AfterViewChecked {
146152
*/
147153
ngAfterViewChecked() {
148154
if (this._isCenterPosition(this._position) && !this._portalHost.hasAttached()) {
149-
this._portalHost.attach(this._content);
155+
this._portalHost.attach(this._contentPortal);
156+
this._contentElement.nativeElement.scrollTop = this._lastScrollPosition;
150157
}
151158
}
152159

@@ -159,6 +166,7 @@ export class MdTabBody implements OnInit, AfterViewChecked {
159166
_onTranslateTabComplete(e: AnimationEvent) {
160167
// If the end state is that the tab is not centered, then detach the content.
161168
if (!this._isCenterPosition(e.toState) && !this._isCenterPosition(this._position)) {
169+
this._lastScrollPosition = this._contentElement.nativeElement.scrollTop || 0;
162170
this._portalHost.detach();
163171
}
164172

@@ -176,7 +184,7 @@ export class MdTabBody implements OnInit, AfterViewChecked {
176184
/** Whether the provided position state is considered center, regardless of origin. */
177185
private _isCenterPosition(position: MdTabBodyPositionState|string): boolean {
178186
return position == 'center' ||
179-
position == 'left-origin-center' ||
180-
position == 'right-origin-center';
187+
position == 'left-origin-center' ||
188+
position == 'right-origin-center';
181189
}
182190
}

src/lib/tabs/tab-group.spec.ts

+35
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,41 @@ describe('MdTabGroup', () => {
259259
expect(component.selectedIndex).toBe(numberOfTabs - 2);
260260
});
261261

262+
it('should preserve the scroll position when switching between tabs', fakeAsync(() => {
263+
const testComponent = fixture.componentInstance;
264+
265+
// Add a lot of content to make one of the tabs scrollable.
266+
testComponent.tabs[1].content = new Array(500).fill('content!').join('\n\n');
267+
fixture.detectChanges();
268+
tick(500);
269+
270+
// Cap the tab group height.
271+
fixture.debugElement.query(By.css('md-tab-group')).nativeElement.style.height = `300px`;
272+
273+
const tabElements = fixture.debugElement.queryAll(By.css('.mat-tab-body-content'))
274+
.map(debugElement => debugElement.nativeElement as HTMLElement);
275+
276+
// Focus the tab with the extra content.
277+
testComponent.selectedIndex = 1;
278+
fixture.detectChanges();
279+
tick(500);
280+
281+
// Ensure that there is content and scroll down 100px.
282+
expect(tabElements[1].offsetHeight).toBeGreaterThan(0, 'Expected tab to have some content.');
283+
tabElements[1].scrollTop = 100;
284+
285+
// Move to another tab.
286+
testComponent.selectedIndex = 0;
287+
fixture.detectChanges();
288+
tick(500);
289+
290+
// Switch back to the tab with the extra content.
291+
testComponent.selectedIndex = 1;
292+
fixture.detectChanges();
293+
tick(500);
294+
295+
expect(tabElements[1].scrollTop).toBe(100, 'Expected scroll position to be restored.');
296+
}));
262297
});
263298

264299
describe('async tabs', () => {

0 commit comments

Comments
 (0)