Skip to content

Commit

Permalink
fix(tabs): re-align the ink bar when the viewport size changes
Browse files Browse the repository at this point in the history
* Recalculates the ink bar position, as well as the pagination, if the viewport size changes.
* Adds a missing test on the `tab-nav-bar` regarding repositioning on direction changes.

Fixes angular#3845.
Fixes angular#3044.
Fixes angular#2518.
Fixes angular#1231.
  • Loading branch information
crisbeto committed Apr 2, 2017
1 parent f40296e commit e8daabf
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 28 deletions.
18 changes: 17 additions & 1 deletion src/lib/tabs/tab-header.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {async, ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing';
import {Component, ViewChild, ViewContainerRef} from '@angular/core';
import {LayoutDirection, Dir} from '../core/rtl/dir';
import {MdTabHeader} from './tab-header';
Expand All @@ -11,6 +11,7 @@ import {RIGHT_ARROW, LEFT_ARROW, ENTER} from '../core/keyboard/keycodes';
import {FakeViewportRuler} from '../core/overlay/position/fake-viewport-ruler';
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
import {dispatchKeyboardEvent} from '../core/testing/dispatch-events';
import {dispatchFakeEvent} from '../core/testing/dispatch-events';
import {Subject} from 'rxjs/Subject';


Expand Down Expand Up @@ -211,6 +212,21 @@ describe('MdTabHeader', () => {
expect(inkBar.alignToElement).toHaveBeenCalled();
});

it('should re-align the ink bar when the window is resized', fakeAsync(() => {
fixture = TestBed.createComponent(SimpleTabHeaderApp);
fixture.detectChanges();

const inkBar = fixture.componentInstance.mdTabHeader._inkBar;

spyOn(inkBar, 'alignToElement');

dispatchFakeEvent(window, 'resize');
tick(10);
fixture.detectChanges();

expect(inkBar.alignToElement).toHaveBeenCalled();
}));

});
});

Expand Down
43 changes: 30 additions & 13 deletions src/lib/tabs/tab-header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,20 @@ import {
AfterContentChecked,
AfterContentInit,
OnDestroy,
NgZone,
} from '@angular/core';
import {RIGHT_ARROW, LEFT_ARROW, ENTER, Dir, LayoutDirection} from '../core';
import {MdTabLabelWrapper} from './tab-label-wrapper';
import {MdInkBar} from './ink-bar';
import {Subscription} from 'rxjs/Subscription';
import {Observable} from 'rxjs/Observable';
import {applyCssTransform} from '../core/style/apply-transform';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/auditTime';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/startWith';


/**
* The directions that scrolling can go in when the header's tabs exceed the header width. 'After'
Expand Down Expand Up @@ -68,8 +75,8 @@ export class MdTabHeader implements AfterContentChecked, AfterContentInit, OnDes
/** Whether the header should scroll to the selected index after the view has been checked. */
private _selectedIndexChanged = false;

/** Subscription to changes in the layout direction. */
private _directionChange: Subscription;
/** Combines listeners that will re-align the ink bar whenever they're invoked. */
private _realignInkBar: Subscription = null;

/** Whether the controls for pagination should be displayed */
_showPaginationControls = false;
Expand All @@ -92,21 +99,25 @@ export class MdTabHeader implements AfterContentChecked, AfterContentInit, OnDes
private _selectedIndex: number = 0;

/** The index of the active tab. */
@Input() set selectedIndex(value: number) {
@Input()
get selectedIndex(): number { return this._selectedIndex; }
set selectedIndex(value: number) {
this._selectedIndexChanged = this._selectedIndex != value;

this._selectedIndex = value;
this._focusIndex = value;
}
get selectedIndex(): number { return this._selectedIndex; }

/** Event emitted when the option is selected. */
@Output() selectFocusedIndex = new EventEmitter();

/** Event emitted when a label is focused. */
@Output() indexFocused = new EventEmitter();

constructor(private _elementRef: ElementRef, @Optional() private _dir: Dir) {}
constructor(
private _elementRef: ElementRef,
private _ngZone: NgZone,
@Optional() private _dir: Dir) { }

ngAfterContentChecked(): void {
// If the number of tab labels have changed, check if scrolling should be enabled
Expand Down Expand Up @@ -150,17 +161,23 @@ export class MdTabHeader implements AfterContentChecked, AfterContentInit, OnDes
* Aligns the ink bar to the selected tab on load.
*/
ngAfterContentInit() {
this._alignInkBarToSelectedTab();

if (this._dir) {
this._directionChange = this._dir.dirChange.subscribe(() => this._alignInkBarToSelectedTab());
}
this._realignInkBar = this._ngZone.runOutsideAngular(() => {
return Observable.merge(
this._dir ? this._dir.dirChange : Observable.of(null),
Observable.fromEvent(window, 'resize').auditTime(10)
)
.startWith(null)
.subscribe(() => {
this._updatePagination();
this._alignInkBarToSelectedTab();
});
});
}

ngOnDestroy() {
if (this._directionChange) {
this._directionChange.unsubscribe();
this._directionChange = null;
if (this._realignInkBar) {
this._realignInkBar.unsubscribe();
this._realignInkBar = null;
}
}

Expand Down
39 changes: 36 additions & 3 deletions src/lib/tabs/tab-nav-bar/tab-nav-bar.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {async, ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing';
import {MdTabsModule} from '../index';
import {Component} from '@angular/core';
import {MdTabNavBar} from './tab-nav-bar';
import {Component, ViewChild} from '@angular/core';
import {By} from '@angular/platform-browser';
import {ViewportRuler} from '../../core/overlay/position/viewport-ruler';
import {FakeViewportRuler} from '../../core/overlay/position/fake-viewport-ruler';
import {dispatchMouseEvent} from '../../core/testing/dispatch-events';
import {dispatchMouseEvent, dispatchFakeEvent} from '../../core/testing/dispatch-events';
import {LayoutDirection, Dir} from '../../core/rtl/dir';
import {Subject} from 'rxjs/Subject';


describe('MdTabNavBar', () => {
let dir: LayoutDirection = 'ltr';
let dirChange = new Subject();

beforeEach(async(() => {
TestBed.configureTestingModule({
Expand All @@ -17,6 +22,9 @@ describe('MdTabNavBar', () => {
TabLinkWithNgIf,
],
providers: [
{provide: Dir, useFactory: () => {
return {value: dir, dirChange: dirChange.asObservable()};
}},
{provide: ViewportRuler, useClass: FakeViewportRuler},
]
});
Expand Down Expand Up @@ -44,6 +52,29 @@ describe('MdTabNavBar', () => {
tabLink.nativeElement.click();
expect(component.activeIndex).toBe(2);
});

it('should re-align the ink bar when the direction changes', () => {
const inkBar = fixture.componentInstance.tabNavBar._inkBar;

spyOn(inkBar, 'alignToElement');

dirChange.next();
fixture.detectChanges();

expect(inkBar.alignToElement).toHaveBeenCalled();
});

it('should re-align the ink bar when the window is resized', fakeAsync(() => {
const inkBar = fixture.componentInstance.tabNavBar._inkBar;

spyOn(inkBar, 'alignToElement');

dispatchFakeEvent(window, 'resize');
tick(10);
fixture.detectChanges();

expect(inkBar.alignToElement).toHaveBeenCalled();
}));
});

it('should clean up the ripple event handlers on destroy', () => {
Expand Down Expand Up @@ -73,6 +104,8 @@ describe('MdTabNavBar', () => {
`
})
class SimpleTabNavBarTestApp {
@ViewChild(MdTabNavBar) tabNavBar: MdTabNavBar;

activeIndex = 0;
}

Expand Down
36 changes: 25 additions & 11 deletions src/lib/tabs/tab-nav-bar/tab-nav-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@ import {
Inject,
Optional,
OnDestroy,
AfterContentInit,
} from '@angular/core';
import {MdInkBar} from '../ink-bar';
import {MdRipple} from '../../core/ripple/index';
import {ViewportRuler} from '../../core/overlay/position/viewport-ruler';
import {MD_RIPPLE_GLOBAL_OPTIONS, RippleGlobalOptions, Dir} from '../../core';
import {Observable} from 'rxjs/Observable';
import {Subscription} from 'rxjs/Subscription';
import 'rxjs/add/operator/auditTime';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/merge';

/**
* Navigation component matching the styles of the tab group header.
Expand All @@ -30,25 +35,32 @@ import {Subscription} from 'rxjs/Subscription';
},
encapsulation: ViewEncapsulation.None,
})
export class MdTabNavBar implements OnDestroy {
private _directionChange: Subscription;
export class MdTabNavBar implements AfterContentInit, OnDestroy {
/** Combines listeners that will re-align the ink bar whenever they're invoked. */
private _realignInkBar: Subscription = null;

_activeLinkChanged: boolean;
_activeLinkElement: ElementRef;

@ViewChild(MdInkBar) _inkBar: MdInkBar;

constructor(@Optional() private _dir: Dir) {
if (_dir) {
this._directionChange = _dir.dirChange.subscribe(() => this._alignInkBar());
}
}
constructor(@Optional() private _dir: Dir, private _ngZone: NgZone) { }

/** Notifies the component that the active link has been changed. */
updateActiveLink(element: ElementRef) {
this._activeLinkChanged = this._activeLinkElement != element;
this._activeLinkElement = element;
}

ngAfterContentInit(): void {
this._realignInkBar = this._ngZone.runOutsideAngular(() => {
return Observable.merge(
this._dir ? this._dir.dirChange : Observable.of(null),
Observable.fromEvent(window, 'resize').auditTime(10)
).subscribe(() => this._alignInkBar());
});
}

/** Checks if the active link has been changed and, if so, will update the ink bar. */
ngAfterContentChecked(): void {
if (this._activeLinkChanged) {
Expand All @@ -58,15 +70,17 @@ export class MdTabNavBar implements OnDestroy {
}

ngOnDestroy() {
if (this._directionChange) {
this._directionChange.unsubscribe();
this._directionChange = null;
if (this._realignInkBar) {
this._realignInkBar.unsubscribe();
this._realignInkBar = null;
}
}

/** Aligns the ink bar to the active link. */
private _alignInkBar(): void {
this._inkBar.alignToElement(this._activeLinkElement.nativeElement);
if (this._activeLinkElement) {
this._inkBar.alignToElement(this._activeLinkElement.nativeElement);
}
}
}

Expand Down

0 comments on commit e8daabf

Please sign in to comment.