Skip to content

Commit 650d09b

Browse files
committed
fix(tabs): observing tab header label changes to recalculate width
- Uses observe-changes directive that emit an event when the mutation observer notifies a change fixes #2155
1 parent bac0388 commit 650d09b

File tree

7 files changed

+132
-7
lines changed

7 files changed

+132
-7
lines changed

src/lib/core/core.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {NgModule, ModuleWithProviders} from '@angular/core';
22
import {MdLineModule} from './line/line';
33
import {RtlModule} from './rtl/dir';
4+
import {ObserveContentModule} from './observe-content/observe-content';
45
import {MdRippleModule} from './ripple/ripple';
56
import {PortalModule} from './portal/portal-directives';
67
import {OverlayModule} from './overlay/overlay-directives';
@@ -11,6 +12,9 @@ import {OVERLAY_PROVIDERS} from './overlay/overlay';
1112
// RTL
1213
export {Dir, LayoutDirection, RtlModule} from './rtl/dir';
1314

15+
// Mutation Observer
16+
export {ObserveContentModule, ObserveContent} from './observe-content/observe-content';
17+
1418
// Portals
1519
export {
1620
Portal,
@@ -99,8 +103,10 @@ export {NoConflictStyleCompatibilityMode} from './compatibility/no-conflict-mode
99103

100104

101105
@NgModule({
102-
imports: [MdLineModule, RtlModule, MdRippleModule, PortalModule, OverlayModule, A11yModule],
103-
exports: [MdLineModule, RtlModule, MdRippleModule, PortalModule, OverlayModule, A11yModule],
106+
imports: [MdLineModule, RtlModule, ObserveContentModule,
107+
MdRippleModule, PortalModule, OverlayModule, A11yModule],
108+
exports: [MdLineModule, RtlModule, ObserveContentModule,
109+
MdRippleModule, PortalModule, OverlayModule, A11yModule],
104110
})
105111
export class MdCoreModule {
106112
static forRoot(): ModuleWithProviders {
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import {Component} from '@angular/core';
2+
import {async, TestBed} from '@angular/core/testing';
3+
import {ObserveContentModule} from './observe-content';
4+
5+
// TODO(elad): `ProxyZone` doesn't seem to capture the events raised by `MutationObserver` and needs to be investigated
6+
7+
describe('Observe content', () => {
8+
beforeEach(async(() => {
9+
TestBed.configureTestingModule({
10+
imports: [ObserveContentModule],
11+
declarations: [ComponentWithTextContent, ComponentWithChildTextContent],
12+
});
13+
14+
TestBed.compileComponents();
15+
}));
16+
17+
describe('text content change', () => {
18+
it('should call the registered for changes function', done => {
19+
let fixture = TestBed.createComponent(ComponentWithTextContent);
20+
fixture.detectChanges();
21+
22+
// If the hint label is empty, expect no label.
23+
const spy = spyOn(fixture.componentInstance, 'doSomething').and.callFake(() => {
24+
expect(spy.calls.any()).toBe(true);
25+
done();
26+
});
27+
28+
expect(spy.calls.any()).toBe(false);
29+
30+
fixture.componentInstance.text = 'text';
31+
fixture.detectChanges();
32+
});
33+
});
34+
35+
describe('child text content change', () => {
36+
it('should call the registered for changes function', done => {
37+
let fixture = TestBed.createComponent(ComponentWithChildTextContent);
38+
fixture.detectChanges();
39+
40+
// If the hint label is empty, expect no label.
41+
const spy = spyOn(fixture.componentInstance, 'doSomething').and.callFake(() => {
42+
expect(spy.calls.any()).toBe(true);
43+
done();
44+
});
45+
46+
expect(spy.calls.any()).toBe(false);
47+
48+
fixture.componentInstance.text = 'text';
49+
fixture.detectChanges();
50+
});
51+
});
52+
});
53+
54+
55+
@Component({ template: `<div (cdk-observe-content)="doSomething()">{{text}}</div>` })
56+
class ComponentWithTextContent {
57+
text = '';
58+
doSomething() {}
59+
}
60+
61+
@Component({ template: `<div (cdk-observe-content)="doSomething()"><div>{{text}}<div></div>` })
62+
class ComponentWithChildTextContent {
63+
text = '';
64+
doSomething() {}
65+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import {
2+
Directive,
3+
ElementRef,
4+
NgModule,
5+
ModuleWithProviders,
6+
Output,
7+
EventEmitter
8+
} from '@angular/core';
9+
10+
@Directive({
11+
selector: '[cdk-observe-content]'
12+
})
13+
export class ObserveContent {
14+
private _observer: MutationObserver;
15+
16+
@Output('cdk-observe-content') event = new EventEmitter<void>();
17+
18+
constructor(private _elementRef: ElementRef) {}
19+
20+
ngAfterContentInit() {
21+
this._observer = new MutationObserver(mutations => mutations.forEach(() => this.event.emit()));
22+
23+
this._observer.observe(this._elementRef.nativeElement, {
24+
characterData: true,
25+
childList: true,
26+
subtree: true
27+
});
28+
}
29+
}
30+
31+
@NgModule({
32+
exports: [ObserveContent],
33+
declarations: [ObserveContent]
34+
})
35+
export class ObserveContentModule {
36+
static forRoot(): ModuleWithProviders {
37+
return {
38+
ngModule: ObserveContentModule,
39+
providers: []
40+
};
41+
}
42+
}

src/lib/module.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {NgModule, ModuleWithProviders} from '@angular/core';
33
import {
44
MdRippleModule,
55
RtlModule,
6+
ObserveContentModule,
67
PortalModule,
78
OverlayModule,
89
A11yModule,
@@ -67,6 +68,7 @@ const MATERIAL_MODULES = [
6768
PlatformModule,
6869
ProjectionModule,
6970
DefaultStyleCompatibilityModeModule,
71+
ObserveContentModule
7072
];
7173

7274
@NgModule({
@@ -89,6 +91,7 @@ const MATERIAL_MODULES = [
8991
PortalModule.forRoot(),
9092
ProjectionModule.forRoot(),
9193
RtlModule.forRoot(),
94+
ObserveContentModule.forRoot(),
9295

9396
// These modules include providers.
9497
A11yModule.forRoot(),

src/lib/tabs/tab-group.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {MdInkBar} from './ink-bar';
2323
import {Observable} from 'rxjs/Observable';
2424
import 'rxjs/add/operator/map';
2525
import {MdRippleModule} from '../core/ripple/ripple';
26+
import {ObserveContentModule} from '../core/observe-content/observe-content';
2627
import {MdTab} from './tab';
2728
import {MdTabBody} from './tab-body';
2829
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
@@ -188,7 +189,7 @@ export class MdTabGroup {
188189
}
189190

190191
@NgModule({
191-
imports: [CommonModule, PortalModule, MdRippleModule],
192+
imports: [CommonModule, PortalModule, MdRippleModule, ObserveContentModule],
192193
// Don't export all components because some are only to be used internally.
193194
exports: [MdTabGroup, MdTabLabel, MdTab, MdTabNavBar, MdTabLink, MdTabLinkRipple],
194195
declarations: [MdTabGroup, MdTabLabel, MdTab, MdInkBar, MdTabLabelWrapper,

src/lib/tabs/tab-header.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
<div class="md-tab-label-container" #tabListContainer
1010
(keydown)="_handleKeydown($event)">
11-
<div class="md-tab-list" #tabList role="tablist">
11+
<div class="md-tab-list" #tabList role="tablist" (cdk-observe-content)="_updatePagination()">
1212
<ng-content></ng-content>
1313
<md-ink-bar></md-ink-bar>
1414
</div>

src/lib/tabs/tab-header.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,7 @@ export class MdTabHeader {
101101
ngAfterContentChecked(): void {
102102
// If the number of tab labels have changed, check if scrolling should be enabled
103103
if (this._tabLabelCount != this._labelWrappers.length) {
104-
this._checkPaginationEnabled();
105-
this._checkScrollingControls();
106-
this._updateTabScrollPosition();
104+
this._updatePagination();
107105
this._tabLabelCount = this._labelWrappers.length;
108106
}
109107

@@ -150,6 +148,16 @@ export class MdTabHeader {
150148
}
151149
}
152150

151+
/**
152+
* Updating the view whether pagination should be enabled or not
153+
* @private
154+
*/
155+
_updatePagination() {
156+
this._checkPaginationEnabled();
157+
this._checkScrollingControls();
158+
this._updateTabScrollPosition();
159+
}
160+
153161
/** When the focus index is set, we must manually send focus to the correct label */
154162
set focusIndex(value: number) {
155163
if (!this._isValidIndex(value) || this._focusIndex == value) { return; }

0 commit comments

Comments
 (0)