diff --git a/src/lib/core/core.ts b/src/lib/core/core.ts
index 686bfc319a5b..c8c20f9b6228 100644
--- a/src/lib/core/core.ts
+++ b/src/lib/core/core.ts
@@ -1,6 +1,7 @@
import {NgModule, ModuleWithProviders} from '@angular/core';
import {MdLineModule} from './line/line';
import {RtlModule} from './rtl/dir';
+import {ObserveContentModule} from './observe-content/observe-content';
import {MdRippleModule} from './ripple/ripple';
import {PortalModule} from './portal/portal-directives';
import {OverlayModule} from './overlay/overlay-directives';
@@ -11,6 +12,9 @@ import {OVERLAY_PROVIDERS} from './overlay/overlay';
// RTL
export {Dir, LayoutDirection, RtlModule} from './rtl/dir';
+// Mutation Observer
+export {ObserveContentModule, ObserveContent} from './observe-content/observe-content';
+
// Portals
export {
Portal,
@@ -115,6 +119,7 @@ export {NoConflictStyleCompatibilityMode} from './compatibility/no-conflict-mode
MdLineModule,
RtlModule,
MdRippleModule,
+ ObserveContentModule,
PortalModule,
OverlayModule,
A11yModule,
@@ -123,6 +128,7 @@ export {NoConflictStyleCompatibilityMode} from './compatibility/no-conflict-mode
MdLineModule,
RtlModule,
MdRippleModule,
+ ObserveContentModule,
PortalModule,
OverlayModule,
A11yModule,
diff --git a/src/lib/core/observe-content/observe-content.spec.ts b/src/lib/core/observe-content/observe-content.spec.ts
new file mode 100644
index 000000000000..888a37391a0e
--- /dev/null
+++ b/src/lib/core/observe-content/observe-content.spec.ts
@@ -0,0 +1,68 @@
+import {Component} from '@angular/core';
+import {async, TestBed} from '@angular/core/testing';
+import {ObserveContentModule} from './observe-content';
+
+/**
+ * TODO(elad): `ProxyZone` doesn't seem to capture the events raised by
+ * `MutationObserver` and needs to be investigated
+ */
+
+describe('Observe content', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [ObserveContentModule],
+ declarations: [ComponentWithTextContent, ComponentWithChildTextContent],
+ });
+
+ TestBed.compileComponents();
+ }));
+
+ describe('text content change', () => {
+ it('should call the registered for changes function', done => {
+ let fixture = TestBed.createComponent(ComponentWithTextContent);
+ fixture.detectChanges();
+
+ // If the hint label is empty, expect no label.
+ const spy = spyOn(fixture.componentInstance, 'doSomething').and.callFake(() => {
+ expect(spy.calls.any()).toBe(true);
+ done();
+ });
+
+ expect(spy.calls.any()).toBe(false);
+
+ fixture.componentInstance.text = 'text';
+ fixture.detectChanges();
+ });
+ });
+
+ describe('child text content change', () => {
+ it('should call the registered for changes function', done => {
+ let fixture = TestBed.createComponent(ComponentWithChildTextContent);
+ fixture.detectChanges();
+
+ // If the hint label is empty, expect no label.
+ const spy = spyOn(fixture.componentInstance, 'doSomething').and.callFake(() => {
+ expect(spy.calls.any()).toBe(true);
+ done();
+ });
+
+ expect(spy.calls.any()).toBe(false);
+
+ fixture.componentInstance.text = 'text';
+ fixture.detectChanges();
+ });
+ });
+});
+
+
+@Component({ template: `
{{text}}
` })
+class ComponentWithTextContent {
+ text = '';
+ doSomething() {}
+}
+
+@Component({ template: `{{text}}
` })
+class ComponentWithChildTextContent {
+ text = '';
+ doSomething() {}
+}
diff --git a/src/lib/core/observe-content/observe-content.ts b/src/lib/core/observe-content/observe-content.ts
new file mode 100644
index 000000000000..23a86b496e29
--- /dev/null
+++ b/src/lib/core/observe-content/observe-content.ts
@@ -0,0 +1,50 @@
+import {
+ Directive,
+ ElementRef,
+ NgModule,
+ ModuleWithProviders,
+ Output,
+ EventEmitter,
+ OnDestroy,
+ AfterContentInit
+} from '@angular/core';
+
+@Directive({
+ selector: '[cdkObserveContent]'
+})
+export class ObserveContent implements AfterContentInit, OnDestroy {
+ private _observer: MutationObserver;
+
+ @Output('cdkObserveContent') event = new EventEmitter
();
+
+ constructor(private _elementRef: ElementRef) {}
+
+ ngAfterContentInit() {
+ this._observer = new MutationObserver(mutations => mutations.forEach(() => this.event.emit()));
+
+ this._observer.observe(this._elementRef.nativeElement, {
+ characterData: true,
+ childList: true,
+ subtree: true
+ });
+ }
+
+ ngOnDestroy() {
+ if (this._observer) {
+ this._observer.disconnect();
+ }
+ }
+}
+
+@NgModule({
+ exports: [ObserveContent],
+ declarations: [ObserveContent]
+})
+export class ObserveContentModule {
+ static forRoot(): ModuleWithProviders {
+ return {
+ ngModule: ObserveContentModule,
+ providers: []
+ };
+ }
+}
diff --git a/src/lib/module.ts b/src/lib/module.ts
index c74a5bcf61a5..2847cde70f9e 100644
--- a/src/lib/module.ts
+++ b/src/lib/module.ts
@@ -3,6 +3,7 @@ import {NgModule, ModuleWithProviders} from '@angular/core';
import {
MdRippleModule,
RtlModule,
+ ObserveContentModule,
PortalModule,
OverlayModule,
A11yModule,
@@ -67,6 +68,7 @@ const MATERIAL_MODULES = [
PlatformModule,
ProjectionModule,
DefaultStyleCompatibilityModeModule,
+ ObserveContentModule
];
@NgModule({
@@ -89,6 +91,7 @@ const MATERIAL_MODULES = [
PortalModule.forRoot(),
ProjectionModule.forRoot(),
RtlModule.forRoot(),
+ ObserveContentModule.forRoot(),
// These modules include providers.
A11yModule.forRoot(),
diff --git a/src/lib/tabs/tab-group.ts b/src/lib/tabs/tab-group.ts
index 91f919b4450d..bd887d2c5254 100644
--- a/src/lib/tabs/tab-group.ts
+++ b/src/lib/tabs/tab-group.ts
@@ -23,6 +23,7 @@ import {MdInkBar} from './ink-bar';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import {MdRippleModule} from '../core/ripple/ripple';
+import {ObserveContentModule} from '../core/observe-content/observe-content';
import {MdTab} from './tab';
import {MdTabBody} from './tab-body';
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
@@ -194,7 +195,7 @@ export class MdTabGroup {
}
@NgModule({
- imports: [CommonModule, PortalModule, MdRippleModule],
+ imports: [CommonModule, PortalModule, MdRippleModule, ObserveContentModule],
// Don't export all components because some are only to be used internally.
exports: [MdTabGroup, MdTabLabel, MdTab, MdTabNavBar, MdTabLink, MdTabLinkRipple],
declarations: [MdTabGroup, MdTabLabel, MdTab, MdInkBar, MdTabLabelWrapper,
diff --git a/src/lib/tabs/tab-header.html b/src/lib/tabs/tab-header.html
index 238c98f7d8e1..ad18fb49bbad 100644
--- a/src/lib/tabs/tab-header.html
+++ b/src/lib/tabs/tab-header.html
@@ -8,7 +8,7 @@
-
+
diff --git a/src/lib/tabs/tab-header.ts b/src/lib/tabs/tab-header.ts
index 1eea13700a66..29f231ba10ca 100644
--- a/src/lib/tabs/tab-header.ts
+++ b/src/lib/tabs/tab-header.ts
@@ -101,9 +101,7 @@ export class MdTabHeader {
ngAfterContentChecked(): void {
// If the number of tab labels have changed, check if scrolling should be enabled
if (this._tabLabelCount != this._labelWrappers.length) {
- this._checkPaginationEnabled();
- this._checkScrollingControls();
- this._updateTabScrollPosition();
+ this._updatePagination();
this._tabLabelCount = this._labelWrappers.length;
}
@@ -150,6 +148,15 @@ export class MdTabHeader {
}
}
+ /**
+ * Updating the view whether pagination should be enabled or not
+ */
+ _updatePagination() {
+ this._checkPaginationEnabled();
+ this._checkScrollingControls();
+ this._updateTabScrollPosition();
+ }
+
/** When the focus index is set, we must manually send focus to the correct label */
set focusIndex(value: number) {
if (!this._isValidIndex(value) || this._focusIndex == value) { return; }