Skip to content

Commit 1bf8eb9

Browse files
committed
Move tabs to smart portal directive.
1 parent 3571f68 commit 1bf8eb9

File tree

7 files changed

+91
-62
lines changed

7 files changed

+91
-62
lines changed

src/lib/tabs/public_api.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@
99
export * from './tabs-module';
1010
export * from './tab-group';
1111
export {MdInkBar} from './ink-bar';
12-
export {MdTabBody, MdTabBodyOriginState, MdTabBodyPositionState} from './tab-body';
12+
export {
13+
MdTabBody,
14+
MdTabBodyOriginState,
15+
MdTabBodyPositionState,
16+
MdTabBodyPortal
17+
} from './tab-body';
1318
export {MdTabHeader, ScrollDirection} from './tab-header';
1419
export {MdTabLabelWrapper} from './tab-label-wrapper';
1520
export {MdTab} from './tab';

src/lib/tabs/tab-body.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
[@translateTab]="_position"
33
(@translateTab.start)="_onTranslateTabStarted($event)"
44
(@translateTab.done)="_onTranslateTabComplete($event)">
5-
<ng-template cdkPortalHost></ng-template>
5+
<ng-template matTabBodyHost></ng-template>
66
</div>

src/lib/tabs/tab-body.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@
66
overflow: hidden;
77
}
88
}
9+
10+
.mat-tab-body-hidden {
11+
display: none;
12+
}

src/lib/tabs/tab-body.spec.ts

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import {Direction, Directionality} from '@angular/cdk/bidi';
22
import {PortalModule, TemplatePortal} from '@angular/cdk/portal';
33
import {CommonModule} from '@angular/common';
44
import {Component, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
5-
import {async, ComponentFixture, fakeAsync, flushMicrotasks, TestBed} from '@angular/core/testing';
5+
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
66
import {MdRippleModule} from '@angular/material/core';
77
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
8-
import {MdTabBody} from './tab-body';
8+
import {MdTabBody, MdTabBodyPortal} from './tab-body';
99

1010

1111
describe('MdTabBody', () => {
@@ -17,6 +17,7 @@ describe('MdTabBody', () => {
1717
imports: [CommonModule, PortalModule, MdRippleModule, NoopAnimationsModule],
1818
declarations: [
1919
MdTabBody,
20+
MdTabBodyPortal,
2021
SimpleTabBodyApp,
2122
],
2223
providers: [
@@ -145,30 +146,6 @@ describe('MdTabBody', () => {
145146
expect(fixture.componentInstance.mdTabBody._position).toBe('left');
146147
});
147148
});
148-
149-
describe('on centered', () => {
150-
let fixture: ComponentFixture<SimpleTabBodyApp>;
151-
152-
beforeEach(fakeAsync(() => {
153-
fixture = TestBed.createComponent(SimpleTabBodyApp);
154-
}));
155-
156-
it('should attach the content when centered and detach when not', fakeAsync(() => {
157-
fixture.componentInstance.position = 1;
158-
fixture.detectChanges();
159-
expect(fixture.componentInstance.mdTabBody._portalHost.hasAttached()).toBe(false);
160-
161-
fixture.componentInstance.position = 0;
162-
fixture.detectChanges();
163-
expect(fixture.componentInstance.mdTabBody._portalHost.hasAttached()).toBe(true);
164-
165-
fixture.componentInstance.position = 1;
166-
fixture.detectChanges();
167-
flushMicrotasks(); // Finish animation and let it detach in animation done handler
168-
expect(fixture.componentInstance.mdTabBody._portalHost.hasAttached()).toBe(false);
169-
}));
170-
});
171-
172149
});
173150

174151

src/lib/tabs/tab-body.ts

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,21 @@
77
*/
88

99
import {
10-
ViewChild,
1110
Component,
1211
Input,
12+
Inject,
1313
Output,
1414
EventEmitter,
15+
OnDestroy,
1516
OnInit,
1617
ElementRef,
18+
Directive,
1719
Optional,
18-
AfterViewChecked,
1920
ViewEncapsulation,
2021
ChangeDetectionStrategy,
22+
ComponentFactoryResolver,
23+
ViewContainerRef,
24+
forwardRef,
2125
} from '@angular/core';
2226
import {
2327
trigger,
@@ -29,7 +33,10 @@ import {
2933
} from '@angular/animations';
3034
import {TemplatePortal, PortalHostDirective} from '@angular/cdk/portal';
3135
import {Directionality, Direction} from '@angular/cdk/bidi';
36+
import {Subscription} from 'rxjs/Subscription';
3237

38+
/** Workaround for https://github.com/angular/angular/issues/17849 */
39+
export const _MdTabBodyPortalBaseClass = PortalHostDirective;
3340

3441
/**
3542
* These position states are used internally as animation states for the tab body. Setting the
@@ -52,6 +59,44 @@ export type MdTabBodyPositionState =
5259
*/
5360
export type MdTabBodyOriginState = 'left' | 'right';
5461

62+
/**
63+
* The portal host directive for the contents of the tab.
64+
* @docs-private
65+
*/
66+
@Directive({
67+
selector: '[matTabBodyHost]'
68+
})
69+
export class MdTabBodyPortal extends _MdTabBodyPortalBaseClass implements OnInit, OnDestroy {
70+
/** A subscription to events for when the tab body begins centering. */
71+
private _centeringSub: Subscription;
72+
73+
constructor(
74+
_componentFactoryResolver: ComponentFactoryResolver,
75+
_viewContainerRef: ViewContainerRef,
76+
@Inject(forwardRef(() => MdTabBody)) private _host: MdTabBody) {
77+
super(_componentFactoryResolver, _viewContainerRef);
78+
}
79+
80+
/** Set initial visibility or set up subscription for changing visibility. */
81+
ngOnInit(): void {
82+
if (this._host._isCenterPosition(this._host._position)) {
83+
this.attach(this._host._content);
84+
} else {
85+
this._centeringSub = this._host._beforeCentering.subscribe(() => {
86+
this.attach(this._host._content);
87+
this._centeringSub.unsubscribe();
88+
});
89+
}
90+
}
91+
92+
/** Clean up subscription if necessary. */
93+
ngOnDestroy(): void {
94+
if (this._centeringSub && !this._centeringSub.closed) {
95+
this._centeringSub.unsubscribe();
96+
}
97+
}
98+
}
99+
55100
/**
56101
* Wrapper for the contents of a tab.
57102
* @docs-private
@@ -88,13 +133,14 @@ export type MdTabBodyOriginState = 'left' | 'right';
88133
])
89134
]
90135
})
91-
export class MdTabBody implements OnInit, AfterViewChecked {
92-
/** The portal host inside of this container into which the tab body content will be loaded. */
93-
@ViewChild(PortalHostDirective) _portalHost: PortalHostDirective;
136+
export class MdTabBody implements OnInit {
94137

95138
/** Event emitted when the tab begins to animate towards the center as the active tab. */
96139
@Output() onCentering: EventEmitter<number> = new EventEmitter<number>();
97140

141+
/** Event emitted before the centering of the tab begins. */
142+
@Output() _beforeCentering: EventEmitter<number> = new EventEmitter<number>();
143+
98144
/** Event emitted when the tab completes its animation towards the center. */
99145
@Output() onCentered: EventEmitter<void> = new EventEmitter<void>(true);
100146

@@ -141,28 +187,14 @@ export class MdTabBody implements OnInit, AfterViewChecked {
141187
}
142188
}
143189

144-
/**
145-
* After the view has been set, check if the tab content is set to the center and attach the
146-
* content if it is not already attached.
147-
*/
148-
ngAfterViewChecked() {
149-
if (this._isCenterPosition(this._position) && !this._portalHost.hasAttached()) {
150-
this._portalHost.attach(this._content);
151-
}
152-
}
153-
154-
_onTranslateTabStarted(e: AnimationEvent) {
190+
_onTranslateTabStarted(e: AnimationEvent): void {
155191
if (this._isCenterPosition(e.toState)) {
192+
this._beforeCentering.emit();
156193
this.onCentering.emit(this._elementRef.nativeElement.clientHeight);
157194
}
158195
}
159196

160-
_onTranslateTabComplete(e: AnimationEvent) {
161-
// If the end state is that the tab is not centered, then detach the content.
162-
if (!this._isCenterPosition(e.toState) && !this._isCenterPosition(this._position)) {
163-
this._portalHost.detach();
164-
}
165-
197+
_onTranslateTabComplete(e: AnimationEvent): void {
166198
// If the transition to the center is complete, emit an event.
167199
if (this._isCenterPosition(e.toState) && this._isCenterPosition(this._position)) {
168200
this.onCentered.emit();
@@ -175,7 +207,7 @@ export class MdTabBody implements OnInit, AfterViewChecked {
175207
}
176208

177209
/** Whether the provided position state is considered center, regardless of origin. */
178-
private _isCenterPosition(position: MdTabBodyPositionState|string): boolean {
210+
_isCenterPosition(position: MdTabBodyPositionState|string): boolean {
179211
return position == 'center' ||
180212
position == 'left-origin-center' ||
181213
position == 'right-origin-center';

src/lib/tabs/tab-group.spec.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,22 @@ describe('MdTabGroup', () => {
3030

3131
describe('basic behavior', () => {
3232
let fixture: ComponentFixture<SimpleTabsTestApp>;
33+
let element: HTMLElement;
3334

3435
beforeEach(() => {
3536
fixture = TestBed.createComponent(SimpleTabsTestApp);
37+
element = fixture.nativeElement;
3638
});
3739

3840
it('should default to the first tab', () => {
3941
checkSelectedIndex(1, fixture);
4042
});
4143

44+
it('will properly load content on first change detection pass', () => {
45+
fixture.detectChanges();
46+
expect(element.querySelectorAll('.mat-tab-body')[1].querySelectorAll('span').length).toBe(3);
47+
});
48+
4249
it('should change selected index on click', () => {
4350
let component = fixture.debugElement.componentInstance;
4451
component.selectedIndex = 0;
@@ -322,23 +329,26 @@ describe('MdTabGroup', () => {
322329
fixture.debugElement.query(By.directive(MdTabGroup)).componentInstance as MdTabGroup;
323330
});
324331

325-
it('should support a tab-group with the simple api', () => {
332+
it('should support a tab-group with the simple api', async(() => {
326333
expect(getSelectedLabel(fixture).textContent).toMatch('Junk food');
327334
expect(getSelectedContent(fixture).textContent).toMatch('Pizza, fries');
328335

329336
tabGroup.selectedIndex = 2;
330337
fixture.detectChanges();
338+
// Use whenStable to wait for async observables and change detection to run in content.
339+
fixture.whenStable().then(() => {
331340

332-
expect(getSelectedLabel(fixture).textContent).toMatch('Fruit');
333-
expect(getSelectedContent(fixture).textContent).toMatch('Apples, grapes');
341+
expect(getSelectedLabel(fixture).textContent).toMatch('Fruit');
342+
expect(getSelectedContent(fixture).textContent).toMatch('Apples, grapes');
334343

335-
fixture.componentInstance.otherLabel = 'Chips';
336-
fixture.componentInstance.otherContent = 'Salt, vinegar';
337-
fixture.detectChanges();
344+
fixture.componentInstance.otherLabel = 'Chips';
345+
fixture.componentInstance.otherContent = 'Salt, vinegar';
346+
fixture.detectChanges();
338347

339-
expect(getSelectedLabel(fixture).textContent).toMatch('Chips');
340-
expect(getSelectedContent(fixture).textContent).toMatch('Salt, vinegar');
341-
});
348+
expect(getSelectedLabel(fixture).textContent).toMatch('Chips');
349+
expect(getSelectedContent(fixture).textContent).toMatch('Salt, vinegar');
350+
});
351+
}));
342352

343353
it('should support @ViewChild in the tab content', () => {
344354
expect(fixture.componentInstance.legumes).toBeTruthy();
@@ -419,7 +429,7 @@ describe('nested MdTabGroup with enabled animations', () => {
419429
</md-tab>
420430
<md-tab>
421431
<ng-template md-tab-label>Tab Two</ng-template>
422-
Tab two content
432+
<span>Tab </span><span>two</span><span>content</span>
423433
</md-tab>
424434
<md-tab>
425435
<ng-template md-tab-label>Tab Three</ng-template>

src/lib/tabs/tabs-module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {NgModule} from '@angular/core';
1414
import {MdCommonModule, MdRippleModule} from '@angular/material/core';
1515
import {MdInkBar} from './ink-bar';
1616
import {MdTab} from './tab';
17-
import {MdTabBody} from './tab-body';
17+
import {MdTabBody, MdTabBodyPortal} from './tab-body';
1818
import {MdTabGroup} from './tab-group';
1919
import {MdTabHeader} from './tab-header';
2020
import {MdTabLabel} from './tab-label';
@@ -41,6 +41,7 @@ import {MdTabLink, MdTabNav} from './tab-nav-bar/tab-nav-bar';
4141
MdTabLink,
4242
],
4343
declarations: [
44+
MdTabBodyPortal,
4445
MdTabGroup,
4546
MdTabLabel,
4647
MdTab,

0 commit comments

Comments
 (0)