Skip to content

Commit 07793a4

Browse files
crisbetotinayuangao
authored andcommitted
fix(tabs): re-align ink bar on direction change (#3622)
Re-aligns the ink bar if the user's layout direction changed. Fixes #3615.
1 parent 52ea7a3 commit 07793a4

File tree

4 files changed

+83
-24
lines changed

4 files changed

+83
-24
lines changed

src/lib/tabs/ink-bar.ts

+14-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Directive, Renderer, ElementRef} from '@angular/core';
1+
import {Directive, Renderer, ElementRef, NgZone} from '@angular/core';
22

33

44
/**
@@ -12,7 +12,10 @@ import {Directive, Renderer, ElementRef} from '@angular/core';
1212
},
1313
})
1414
export class MdInkBar {
15-
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
15+
constructor(
16+
private _renderer: Renderer,
17+
private _elementRef: ElementRef,
18+
private _ngZone: NgZone) {}
1619

1720
/**
1821
* Calculates the styles from the provided element in order to align the ink-bar to that element.
@@ -21,10 +24,15 @@ export class MdInkBar {
2124
*/
2225
alignToElement(element: HTMLElement) {
2326
this.show();
24-
this._renderer.setElementStyle(this._elementRef.nativeElement, 'left',
25-
this._getLeftPosition(element));
26-
this._renderer.setElementStyle(this._elementRef.nativeElement, 'width',
27-
this._getElementWidth(element));
27+
28+
this._ngZone.runOutsideAngular(() => {
29+
requestAnimationFrame(() => {
30+
this._renderer.setElementStyle(this._elementRef.nativeElement, 'left',
31+
this._getLeftPosition(element));
32+
this._renderer.setElementStyle(this._elementRef.nativeElement, 'width',
33+
this._getElementWidth(element));
34+
});
35+
});
2836
}
2937

3038
/** Shows the ink bar. */

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

+21-3
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import {RIGHT_ARROW, LEFT_ARROW, ENTER} from '../core/keyboard/keycodes';
1111
import {FakeViewportRuler} from '../core/overlay/position/fake-viewport-ruler';
1212
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
1313
import {dispatchKeyboardEvent} from '../core/testing/dispatch-events';
14+
import {Subject} from 'rxjs/Subject';
1415

1516

1617
describe('MdTabHeader', () => {
1718
let dir: LayoutDirection = 'ltr';
19+
let dirChange = new Subject();
1820
let fixture: ComponentFixture<SimpleTabHeaderApp>;
1921
let appComponent: SimpleTabHeaderApp;
2022

@@ -29,7 +31,9 @@ describe('MdTabHeader', () => {
2931
SimpleTabHeaderApp,
3032
],
3133
providers: [
32-
{provide: Dir, useFactory: () => { return {value: dir}; }},
34+
{provide: Dir, useFactory: () => {
35+
return {value: dir, dirChange: dirChange.asObservable()};
36+
}},
3337
{provide: ViewportRuler, useClass: FakeViewportRuler},
3438
]
3539
});
@@ -192,8 +196,22 @@ describe('MdTabHeader', () => {
192196
expect(appComponent.mdTabHeader.scrollDistance).toBe(0);
193197
});
194198
});
195-
});
196199

200+
it('should re-align the ink bar when the direction changes', () => {
201+
fixture = TestBed.createComponent(SimpleTabHeaderApp);
202+
fixture.detectChanges();
203+
204+
const inkBar = fixture.componentInstance.mdTabHeader._inkBar;
205+
206+
spyOn(inkBar, 'alignToElement');
207+
208+
dirChange.next();
209+
fixture.detectChanges();
210+
211+
expect(inkBar.alignToElement).toHaveBeenCalled();
212+
});
213+
214+
});
197215
});
198216

199217
interface Tab {
@@ -211,7 +229,7 @@ interface Tab {
211229
*ngFor="let tab of tabs; let i = index"
212230
[disabled]="!!tab.disabled"
213231
(click)="selectedIndex = i">
214-
{{tab.label}}
232+
{{tab.label}}
215233
</div>
216234
</md-tab-header>
217235
</div>

src/lib/tabs/tab-header.ts

+20-11
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {
22
ViewChild,
33
Component,
44
Input,
5-
NgZone,
65
QueryList,
76
ElementRef,
87
ViewEncapsulation,
@@ -12,12 +11,14 @@ import {
1211
Optional,
1312
AfterContentChecked,
1413
AfterContentInit,
14+
OnDestroy,
1515
} from '@angular/core';
1616
import {RIGHT_ARROW, LEFT_ARROW, ENTER, Dir, LayoutDirection} from '../core';
1717
import {MdTabLabelWrapper} from './tab-label-wrapper';
1818
import {MdInkBar} from './ink-bar';
19-
import 'rxjs/add/operator/map';
19+
import {Subscription} from 'rxjs/Subscription';
2020
import {applyCssTransform} from '../core/style/apply-transform';
21+
import 'rxjs/add/operator/map';
2122

2223
/**
2324
* The directions that scrolling can go in when the header's tabs exceed the header width. 'After'
@@ -51,7 +52,7 @@ const EXAGGERATED_OVERSCROLL = 60;
5152
'[class.mat-tab-header-rtl]': "_getLayoutDirection() == 'rtl'",
5253
}
5354
})
54-
export class MdTabHeader implements AfterContentChecked, AfterContentInit {
55+
export class MdTabHeader implements AfterContentChecked, AfterContentInit, OnDestroy {
5556
@ContentChildren(MdTabLabelWrapper) _labelWrappers: QueryList<MdTabLabelWrapper>;
5657

5758
@ViewChild(MdInkBar) _inkBar: MdInkBar;
@@ -67,6 +68,9 @@ export class MdTabHeader implements AfterContentChecked, AfterContentInit {
6768
/** Whether the header should scroll to the selected index after the view has been checked. */
6869
private _selectedIndexChanged = false;
6970

71+
/** Subscription to changes in the layout direction. */
72+
private _directionChange: Subscription;
73+
7074
/** Whether the controls for pagination should be displayed */
7175
_showPaginationControls = false;
7276

@@ -102,9 +106,7 @@ export class MdTabHeader implements AfterContentChecked, AfterContentInit {
102106
/** Event emitted when a label is focused. */
103107
@Output() indexFocused = new EventEmitter();
104108

105-
constructor(private _zone: NgZone,
106-
private _elementRef: ElementRef,
107-
@Optional() private _dir: Dir) {}
109+
constructor(private _elementRef: ElementRef, @Optional() private _dir: Dir) {}
108110

109111
ngAfterContentChecked(): void {
110112
// If the number of tab labels have changed, check if scrolling should be enabled
@@ -149,6 +151,17 @@ export class MdTabHeader implements AfterContentChecked, AfterContentInit {
149151
*/
150152
ngAfterContentInit() {
151153
this._alignInkBarToSelectedTab();
154+
155+
if (this._dir) {
156+
this._directionChange = this._dir.dirChange.subscribe(() => this._alignInkBarToSelectedTab());
157+
}
158+
}
159+
160+
ngOnDestroy() {
161+
if (this._directionChange) {
162+
this._directionChange.unsubscribe();
163+
this._directionChange = null;
164+
}
152165
}
153166

154167
/**
@@ -373,10 +386,6 @@ export class MdTabHeader implements AfterContentChecked, AfterContentInit {
373386
? this._labelWrappers.toArray()[this.selectedIndex].elementRef.nativeElement
374387
: null;
375388

376-
this._zone.runOutsideAngular(() => {
377-
requestAnimationFrame(() => {
378-
this._inkBar.alignToElement(selectedLabelWrapper);
379-
});
380-
});
389+
this._inkBar.alignToElement(selectedLabelWrapper);
381390
}
382391
}

src/lib/tabs/tab-nav-bar/tab-nav-bar.ts

+28-4
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,17 @@ import {
44
ViewChild,
55
ElementRef,
66
ViewEncapsulation,
7-
Directive, NgZone, Inject, Optional,
7+
Directive,
8+
NgZone,
9+
Inject,
10+
Optional,
11+
OnDestroy,
812
} from '@angular/core';
913
import {MdInkBar} from '../ink-bar';
1014
import {MdRipple} from '../../core/ripple/index';
1115
import {ViewportRuler} from '../../core/overlay/position/viewport-ruler';
12-
import {MD_RIPPLE_GLOBAL_OPTIONS, RippleGlobalOptions} from '../../core/ripple/ripple';
16+
import {MD_RIPPLE_GLOBAL_OPTIONS, RippleGlobalOptions, Dir} from '../../core';
17+
import {Subscription} from 'rxjs/Subscription';
1318

1419
/**
1520
* Navigation component matching the styles of the tab group header.
@@ -25,12 +30,19 @@ import {MD_RIPPLE_GLOBAL_OPTIONS, RippleGlobalOptions} from '../../core/ripple/r
2530
},
2631
encapsulation: ViewEncapsulation.None,
2732
})
28-
export class MdTabNavBar {
33+
export class MdTabNavBar implements OnDestroy {
34+
private _directionChange: Subscription;
2935
_activeLinkChanged: boolean;
3036
_activeLinkElement: ElementRef;
3137

3238
@ViewChild(MdInkBar) _inkBar: MdInkBar;
3339

40+
constructor(@Optional() private _dir: Dir) {
41+
if (_dir) {
42+
this._directionChange = _dir.dirChange.subscribe(() => this._alignInkBar());
43+
}
44+
}
45+
3446
/** Notifies the component that the active link has been changed. */
3547
updateActiveLink(element: ElementRef) {
3648
this._activeLinkChanged = this._activeLinkElement != element;
@@ -40,10 +52,22 @@ export class MdTabNavBar {
4052
/** Checks if the active link has been changed and, if so, will update the ink bar. */
4153
ngAfterContentChecked(): void {
4254
if (this._activeLinkChanged) {
43-
this._inkBar.alignToElement(this._activeLinkElement.nativeElement);
55+
this._alignInkBar();
4456
this._activeLinkChanged = false;
4557
}
4658
}
59+
60+
ngOnDestroy() {
61+
if (this._directionChange) {
62+
this._directionChange.unsubscribe();
63+
this._directionChange = null;
64+
}
65+
}
66+
67+
/** Aligns the ink bar to the active link. */
68+
private _alignInkBar(): void {
69+
this._inkBar.alignToElement(this._activeLinkElement.nativeElement);
70+
}
4771
}
4872

4973
/**

0 commit comments

Comments
 (0)