mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(tabs): observing tab header label changes to recalculate width (#2186)
- Uses observe-changes directive that emit an event when the mutation observer notifies a change fixes #2155
This commit is contained in:
committed by
Jeremy Elbourn
parent
f45c315883
commit
7ab4430905
@@ -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,
|
||||
|
||||
@@ -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: `<div (cdkObserveContent)="doSomething()">{{text}}</div>` })
|
||||
class ComponentWithTextContent {
|
||||
text = '';
|
||||
doSomething() {}
|
||||
}
|
||||
|
||||
@Component({ template: `<div (cdkObserveContent)="doSomething()"><div>{{text}}<div></div>` })
|
||||
class ComponentWithChildTextContent {
|
||||
text = '';
|
||||
doSomething() {}
|
||||
}
|
||||
@@ -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<void>();
|
||||
|
||||
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: []
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<div class="md-tab-label-container" #tabListContainer
|
||||
(keydown)="_handleKeydown($event)">
|
||||
<div class="md-tab-list" #tabList role="tablist">
|
||||
<div class="md-tab-list" #tabList role="tablist" (cdkObserveContent)="_updatePagination()">
|
||||
<ng-content></ng-content>
|
||||
<md-ink-bar></md-ink-bar>
|
||||
</div>
|
||||
|
||||
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user