Skip to content

Commit e56f38f

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 86123a3 commit e56f38f

File tree

7 files changed

+113
-7
lines changed

7 files changed

+113
-7
lines changed

src/lib/core/core.ts

Lines changed: 6 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 {MutationObserverModule} 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 {MutationObserverModule, MdMutationObserver} from './observe-content/observe-content';
17+
1418
// Portals
1519
export {
1620
Portal,
@@ -99,8 +103,8 @@ 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, MutationObserverModule, MdRippleModule, PortalModule, OverlayModule, A11yModule],
107+
exports: [MdLineModule, RtlModule, MutationObserverModule, MdRippleModule, PortalModule, OverlayModule, A11yModule],
104108
})
105109
export class MdCoreModule {
106110
static forRoot(): ModuleWithProviders {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import {Component} from '@angular/core';
2+
import {async, TestBed} from '@angular/core/testing';
3+
import {MutationObserverModule} from './observe-content';
4+
5+
describe('Observe content', () => {
6+
beforeEach(async(() => {
7+
TestBed.configureTestingModule({
8+
imports: [MutationObserverModule],
9+
declarations: [ComponentWithTextContent, ComponentWithChildTextContent],
10+
});
11+
12+
TestBed.compileComponents();
13+
}));
14+
15+
describe('text content change', () => {
16+
it('should call the registered for changes function', done => {
17+
let fixture = TestBed.createComponent(ComponentWithTextContent);
18+
fixture.detectChanges();
19+
20+
// If the hint label is empty, expect no label.
21+
const spy = spyOn(fixture.componentInstance, 'doSomething').and.callFake(() => {
22+
expect(spy.calls.any()).toBe(true);
23+
done();
24+
});
25+
26+
expect(spy.calls.any()).toBe(false);
27+
28+
fixture.componentInstance.text = 'text';
29+
fixture.detectChanges();
30+
});
31+
});
32+
33+
describe('child text content change', () => {
34+
it('should call the registered for changes function', done => {
35+
let fixture = TestBed.createComponent(ComponentWithChildTextContent);
36+
fixture.detectChanges();
37+
38+
// If the hint label is empty, expect no label.
39+
const spy = spyOn(fixture.componentInstance, 'doSomething').and.callFake(() => {
40+
expect(spy.calls.any()).toBe(true);
41+
done();
42+
});
43+
44+
expect(spy.calls.any()).toBe(false);
45+
46+
fixture.componentInstance.text = 'text';
47+
fixture.detectChanges();
48+
});
49+
});
50+
});
51+
52+
53+
@Component({ template: `<div (cdk-observe-content)="doSomething()">{{text}}</div>` })
54+
class ComponentWithTextContent {
55+
text = '';
56+
doSomething() {}
57+
}
58+
59+
@Component({ template: `<div (cdk-observe-content)="doSomething()"><div>{{text}}<div></div>` })
60+
class ComponentWithChildTextContent {
61+
text = '';
62+
doSomething() {}
63+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {Directive, ElementRef, NgModule, ModuleWithProviders, Output, EventEmitter} from '@angular/core';
2+
3+
@Directive({
4+
selector: '[cdk-observe-content]',
5+
})
6+
export class MdMutationObserver {
7+
private observer: MutationObserver;
8+
9+
@Output('cdk-observe-content') event = new EventEmitter();
10+
11+
constructor(private elementRef:ElementRef) {}
12+
13+
ngAfterViewInit() {
14+
this.observer = new MutationObserver(mutations => mutations.forEach(() => this.event.emit()));
15+
16+
this.observer.observe(this.elementRef.nativeElement, {characterData: true, subtree: true});
17+
}
18+
}
19+
20+
@NgModule({
21+
exports: [MdMutationObserver],
22+
declarations: [MdMutationObserver]
23+
})
24+
export class MutationObserverModule {
25+
static forRoot(): ModuleWithProviders {
26+
return {
27+
ngModule: MutationObserverModule,
28+
providers: []
29+
};
30+
}
31+
}

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+
MutationObserverModule,
67
PortalModule,
78
OverlayModule,
89
A11yModule,
@@ -67,6 +68,7 @@ const MATERIAL_MODULES = [
6768
PlatformModule,
6869
ProjectionModule,
6970
DefaultStyleCompatibilityModeModule,
71+
MutationObserverModule
7072
];
7173

7274
@NgModule({
@@ -89,6 +91,7 @@ const MATERIAL_MODULES = [
8991
PortalModule.forRoot(),
9092
ProjectionModule.forRoot(),
9193
RtlModule.forRoot(),
94+
MutationObserverModule.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 {MutationObserverModule} 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';
@@ -189,7 +190,7 @@ export class MdTabGroup {
189190
}
190191

191192
@NgModule({
192-
imports: [CommonModule, PortalModule, MdRippleModule],
193+
imports: [CommonModule, PortalModule, MdRippleModule, MutationObserverModule],
193194
// Don't export all components because some are only to be used internally.
194195
exports: [MdTabGroup, MdTabLabel, MdTab, MdTabNavBar, MdTabLink, MdTabLinkRipple],
195196
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)="_checkPagination()">
1212
<ng-content></ng-content>
1313
<md-ink-bar></md-ink-bar>
1414
</div>

src/lib/tabs/tab-header.ts

Lines changed: 7 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._checkPagination();
107105
this._tabLabelCount = this._labelWrappers.length;
108106
}
109107

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

151+
_checkPagination() {
152+
this._checkPaginationEnabled();
153+
this._checkScrollingControls();
154+
this._updateTabScrollPosition();
155+
}
156+
153157
/** When the focus index is set, we must manually send focus to the correct label */
154158
set focusIndex(value: number) {
155159
if (!this._isValidIndex(value) || this._focusIndex == value) { return; }

0 commit comments

Comments
 (0)