diff --git a/src/lib/tabs/public-api.ts b/src/lib/tabs/public-api.ts
index 9d08a2fef02..5bdae581689 100644
--- a/src/lib/tabs/public-api.ts
+++ b/src/lib/tabs/public-api.ts
@@ -9,11 +9,14 @@
export * from './tabs-module';
export * from './tab-group';
export {MatInkBar} from './ink-bar';
-export {MatTabBody, MatTabBodyOriginState, MatTabBodyPositionState} from './tab-body';
+export {
+ MatTabBody,
+ MatTabBodyOriginState,
+ MatTabBodyPositionState,
+ MatTabBodyPortal
+} from './tab-body';
export {MatTabHeader, ScrollDirection} from './tab-header';
export {MatTabLabelWrapper} from './tab-label-wrapper';
export {MatTab} from './tab';
export {MatTabLabel} from './tab-label';
export {MatTabNav, MatTabLink} from './tab-nav-bar/index';
-
-
diff --git a/src/lib/tabs/tab-body.html b/src/lib/tabs/tab-body.html
index 0650b3fdc08..312b1848a07 100644
--- a/src/lib/tabs/tab-body.html
+++ b/src/lib/tabs/tab-body.html
@@ -2,5 +2,5 @@
[@translateTab]="_position"
(@translateTab.start)="_onTranslateTabStarted($event)"
(@translateTab.done)="_onTranslateTabComplete($event)">
-
+
diff --git a/src/lib/tabs/tab-body.spec.ts b/src/lib/tabs/tab-body.spec.ts
index 4572e9f756f..58c9f81edbc 100644
--- a/src/lib/tabs/tab-body.spec.ts
+++ b/src/lib/tabs/tab-body.spec.ts
@@ -2,10 +2,10 @@ import {Direction, Directionality} from '@angular/cdk/bidi';
import {PortalModule, TemplatePortal} from '@angular/cdk/portal';
import {CommonModule} from '@angular/common';
import {Component, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
-import {async, ComponentFixture, fakeAsync, flushMicrotasks, TestBed} from '@angular/core/testing';
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {MatRippleModule} from '@angular/material/core';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
-import {MatTabBody} from './tab-body';
+import {MatTabBody, MatTabBodyPortal} from './tab-body';
describe('MatTabBody', () => {
@@ -17,6 +17,7 @@ describe('MatTabBody', () => {
imports: [CommonModule, PortalModule, MatRippleModule, NoopAnimationsModule],
declarations: [
MatTabBody,
+ MatTabBodyPortal,
SimpleTabBodyApp,
],
providers: [
@@ -145,30 +146,6 @@ describe('MatTabBody', () => {
expect(fixture.componentInstance.tabBody._position).toBe('left');
});
});
-
- describe('on centered', () => {
- let fixture: ComponentFixture;
-
- beforeEach(fakeAsync(() => {
- fixture = TestBed.createComponent(SimpleTabBodyApp);
- }));
-
- it('should attach the content when centered and detach when not', fakeAsync(() => {
- fixture.componentInstance.position = 1;
- fixture.detectChanges();
- expect(fixture.componentInstance.tabBody._portalOutlet.hasAttached()).toBe(false);
-
- fixture.componentInstance.position = 0;
- fixture.detectChanges();
- expect(fixture.componentInstance.tabBody._portalOutlet.hasAttached()).toBe(true);
-
- fixture.componentInstance.position = 1;
- fixture.detectChanges();
- flushMicrotasks(); // Finish animation and let it detach in animation done handler
- expect(fixture.componentInstance.tabBody._portalOutlet.hasAttached()).toBe(false);
- }));
- });
-
});
diff --git a/src/lib/tabs/tab-body.ts b/src/lib/tabs/tab-body.ts
index a0519883e43..e08eb716b14 100644
--- a/src/lib/tabs/tab-body.ts
+++ b/src/lib/tabs/tab-body.ts
@@ -7,17 +7,21 @@
*/
import {
- ViewChild,
Component,
Input,
+ Inject,
Output,
EventEmitter,
+ OnDestroy,
OnInit,
ElementRef,
+ Directive,
Optional,
- AfterViewChecked,
ViewEncapsulation,
ChangeDetectionStrategy,
+ ComponentFactoryResolver,
+ ViewContainerRef,
+ forwardRef,
} from '@angular/core';
import {
trigger,
@@ -29,7 +33,10 @@ import {
} from '@angular/animations';
import {TemplatePortal, CdkPortalOutlet} from '@angular/cdk/portal';
import {Directionality, Direction} from '@angular/cdk/bidi';
+import {Subscription} from 'rxjs/Subscription';
+/** Workaround for https://github.com/angular/angular/issues/17849 */
+export const _MatTabBodyPortalBaseClass = CdkPortalOutlet;
/**
* These position states are used internally as animation states for the tab body. Setting the
@@ -52,6 +59,44 @@ export type MatTabBodyPositionState =
*/
export type MatTabBodyOriginState = 'left' | 'right';
+/**
+ * The portal host directive for the contents of the tab.
+ * @docs-private
+ */
+@Directive({
+ selector: '[matTabBodyHost]'
+})
+export class MatTabBodyPortal extends _MatTabBodyPortalBaseClass implements OnInit, OnDestroy {
+ /** A subscription to events for when the tab body begins centering. */
+ private _centeringSub: Subscription;
+
+ constructor(
+ _componentFactoryResolver: ComponentFactoryResolver,
+ _viewContainerRef: ViewContainerRef,
+ @Inject(forwardRef(() => MatTabBody)) private _host: MatTabBody) {
+ super(_componentFactoryResolver, _viewContainerRef);
+ }
+
+ /** Set initial visibility or set up subscription for changing visibility. */
+ ngOnInit(): void {
+ if (this._host._isCenterPosition(this._host._position)) {
+ this.attach(this._host._content);
+ } else {
+ this._centeringSub = this._host._beforeCentering.subscribe(() => {
+ this.attach(this._host._content);
+ this._centeringSub.unsubscribe();
+ });
+ }
+ }
+
+ /** Clean up subscription if necessary. */
+ ngOnDestroy(): void {
+ if (this._centeringSub && !this._centeringSub.closed) {
+ this._centeringSub.unsubscribe();
+ }
+ }
+}
+
/**
* Wrapper for the contents of a tab.
* @docs-private
@@ -86,13 +131,13 @@ export type MatTabBodyOriginState = 'left' | 'right';
])
]
})
-export class MatTabBody implements OnInit, AfterViewChecked {
- /** The portal outlet inside of this container into which the tab body content will be loaded. */
- @ViewChild(CdkPortalOutlet) _portalOutlet: CdkPortalOutlet;
-
+export class MatTabBody implements OnInit {
/** Event emitted when the tab begins to animate towards the center as the active tab. */
@Output() _onCentering: EventEmitter = new EventEmitter();
+ /** Event emitted before the centering of the tab begins. */
+ @Output() _beforeCentering: EventEmitter = new EventEmitter();
+
/** Event emitted when the tab completes its animation towards the center. */
@Output() _onCentered: EventEmitter = new EventEmitter(true);
@@ -139,28 +184,14 @@ export class MatTabBody implements OnInit, AfterViewChecked {
}
}
- /**
- * After the view has been set, check if the tab content is set to the center and attach the
- * content if it is not already attached.
- */
- ngAfterViewChecked() {
- if (this._isCenterPosition(this._position) && !this._portalOutlet.hasAttached()) {
- this._portalOutlet.attach(this._content);
- }
- }
-
- _onTranslateTabStarted(e: AnimationEvent) {
+ _onTranslateTabStarted(e: AnimationEvent): void {
if (this._isCenterPosition(e.toState)) {
+ this._beforeCentering.emit();
this._onCentering.emit(this._elementRef.nativeElement.clientHeight);
}
}
- _onTranslateTabComplete(e: AnimationEvent) {
- // If the end state is that the tab is not centered, then detach the content.
- if (!this._isCenterPosition(e.toState) && !this._isCenterPosition(this._position)) {
- this._portalOutlet.detach();
- }
-
+ _onTranslateTabComplete(e: AnimationEvent): void {
// If the transition to the center is complete, emit an event.
if (this._isCenterPosition(e.toState) && this._isCenterPosition(this._position)) {
this._onCentered.emit();
@@ -173,7 +204,7 @@ export class MatTabBody implements OnInit, AfterViewChecked {
}
/** Whether the provided position state is considered center, regardless of origin. */
- private _isCenterPosition(position: MatTabBodyPositionState|string): boolean {
+ _isCenterPosition(position: MatTabBodyPositionState|string): boolean {
return position == 'center' ||
position == 'left-origin-center' ||
position == 'right-origin-center';
diff --git a/src/lib/tabs/tab-group.spec.ts b/src/lib/tabs/tab-group.spec.ts
index 5134ee02467..03780b818d2 100644
--- a/src/lib/tabs/tab-group.spec.ts
+++ b/src/lib/tabs/tab-group.spec.ts
@@ -26,15 +26,22 @@ describe('MatTabGroup', () => {
describe('basic behavior', () => {
let fixture: ComponentFixture;
+ let element: HTMLElement;
beforeEach(() => {
fixture = TestBed.createComponent(SimpleTabsTestApp);
+ element = fixture.nativeElement;
});
it('should default to the first tab', () => {
checkSelectedIndex(1, fixture);
});
+ it('will properly load content on first change detection pass', () => {
+ fixture.detectChanges();
+ expect(element.querySelectorAll('.mat-tab-body')[1].querySelectorAll('span').length).toBe(3);
+ });
+
it('should change selected index on click', () => {
let component = fixture.debugElement.componentInstance;
component.selectedIndex = 0;
@@ -318,23 +325,26 @@ describe('MatTabGroup', () => {
fixture.debugElement.query(By.directive(MatTabGroup)).componentInstance as MatTabGroup;
});
- it('should support a tab-group with the simple api', () => {
+ it('should support a tab-group with the simple api', async(() => {
expect(getSelectedLabel(fixture).textContent).toMatch('Junk food');
expect(getSelectedContent(fixture).textContent).toMatch('Pizza, fries');
tabGroup.selectedIndex = 2;
fixture.detectChanges();
+ // Use whenStable to wait for async observables and change detection to run in content.
+ fixture.whenStable().then(() => {
- expect(getSelectedLabel(fixture).textContent).toMatch('Fruit');
- expect(getSelectedContent(fixture).textContent).toMatch('Apples, grapes');
+ expect(getSelectedLabel(fixture).textContent).toMatch('Fruit');
+ expect(getSelectedContent(fixture).textContent).toMatch('Apples, grapes');
- fixture.componentInstance.otherLabel = 'Chips';
- fixture.componentInstance.otherContent = 'Salt, vinegar';
- fixture.detectChanges();
+ fixture.componentInstance.otherLabel = 'Chips';
+ fixture.componentInstance.otherContent = 'Salt, vinegar';
+ fixture.detectChanges();
- expect(getSelectedLabel(fixture).textContent).toMatch('Chips');
- expect(getSelectedContent(fixture).textContent).toMatch('Salt, vinegar');
- });
+ expect(getSelectedLabel(fixture).textContent).toMatch('Chips');
+ expect(getSelectedContent(fixture).textContent).toMatch('Salt, vinegar');
+ });
+ }));
it('should support @ViewChild in the tab content', () => {
expect(fixture.componentInstance.legumes).toBeTruthy();
@@ -415,7 +425,7 @@ describe('nested MatTabGroup with enabled animations', () => {
Tab Two
- Tab two content
+ Tab twocontent
Tab Three
diff --git a/src/lib/tabs/tabs-module.ts b/src/lib/tabs/tabs-module.ts
index 40e4e962cbf..5defcedd2e5 100644
--- a/src/lib/tabs/tabs-module.ts
+++ b/src/lib/tabs/tabs-module.ts
@@ -14,7 +14,7 @@ import {NgModule} from '@angular/core';
import {MatCommonModule, MatRippleModule} from '@angular/material/core';
import {MatInkBar} from './ink-bar';
import {MatTab} from './tab';
-import {MatTabBody} from './tab-body';
+import {MatTabBody, MatTabBodyPortal} from './tab-body';
import {MatTabGroup} from './tab-group';
import {MatTabHeader} from './tab-header';
import {MatTabLabel} from './tab-label';
@@ -49,6 +49,7 @@ import {MatTabLink, MatTabNav} from './tab-nav-bar/tab-nav-bar';
MatTabNav,
MatTabLink,
MatTabBody,
+ MatTabBodyPortal,
MatTabHeader
],
providers: [VIEWPORT_RULER_PROVIDER],