Skip to content

Commit b894fe8

Browse files
committed
Move tabs to smart portal directive.
1 parent b5310da commit b894fe8

File tree

6 files changed

+87
-64
lines changed

6 files changed

+87
-64
lines changed

src/lib/tabs/public-api.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@
99
export * from './tabs-module';
1010
export * from './tab-group';
1111
export {MatInkBar} from './ink-bar';
12-
export {MatTabBody, MatTabBodyOriginState, MatTabBodyPositionState} from './tab-body';
12+
export {
13+
MatTabBody,
14+
MatTabBodyOriginState,
15+
MatTabBodyPositionState,
16+
MatTabBodyPortal
17+
} from './tab-body';
1318
export {MatTabHeader, ScrollDirection} from './tab-header';
1419
export {MatTabLabelWrapper} from './tab-label-wrapper';
1520
export {MatTab} from './tab';
1621
export {MatTabLabel} from './tab-label';
1722
export {MatTabNav, MatTabLink} from './tab-nav-bar/index';
18-
19-

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.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 {MatRippleModule} from '@angular/material/core';
77
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
8-
import {MatTabBody} from './tab-body';
8+
import {MatTabBody, MatTabBodyPortal} from './tab-body';
99

1010

1111
describe('MatTabBody', () => {
@@ -17,6 +17,7 @@ describe('MatTabBody', () => {
1717
imports: [CommonModule, PortalModule, MatRippleModule, NoopAnimationsModule],
1818
declarations: [
1919
MatTabBody,
20+
MatTabBodyPortal,
2021
SimpleTabBodyApp,
2122
],
2223
providers: [
@@ -145,30 +146,6 @@ describe('MatTabBody', () => {
145146
expect(fixture.componentInstance.tabBody._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.tabBody._portalHost.hasAttached()).toBe(false);
160-
161-
fixture.componentInstance.position = 0;
162-
fixture.detectChanges();
163-
expect(fixture.componentInstance.tabBody._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.tabBody._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 _MatTabBodyPortalBaseClass = 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 MatTabBodyPositionState =
5259
*/
5360
export type MatTabBodyOriginState = '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 MatTabBodyPortal extends _MatTabBodyPortalBaseClass 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(() => MatTabBody)) private _host: MatTabBody) {
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
@@ -86,13 +131,14 @@ export type MatTabBodyOriginState = 'left' | 'right';
86131
])
87132
]
88133
})
89-
export class MatTabBody implements OnInit, AfterViewChecked {
90-
/** The portal host inside of this container into which the tab body content will be loaded. */
91-
@ViewChild(PortalHostDirective) _portalHost: PortalHostDirective;
134+
export class MatTabBody implements OnInit {
92135

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

139+
/** Event emitted before the centering of the tab begins. */
140+
@Output() _beforeCentering: EventEmitter<number> = new EventEmitter<number>();
141+
96142
/** Event emitted when the tab completes its animation towards the center. */
97143
@Output() _onCentered: EventEmitter<void> = new EventEmitter<void>(true);
98144

@@ -139,28 +185,14 @@ export class MatTabBody implements OnInit, AfterViewChecked {
139185
}
140186
}
141187

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

158-
_onTranslateTabComplete(e: AnimationEvent) {
159-
// If the end state is that the tab is not centered, then detach the content.
160-
if (!this._isCenterPosition(e.toState) && !this._isCenterPosition(this._position)) {
161-
this._portalHost.detach();
162-
}
163-
195+
_onTranslateTabComplete(e: AnimationEvent): void {
164196
// If the transition to the center is complete, emit an event.
165197
if (this._isCenterPosition(e.toState) && this._isCenterPosition(this._position)) {
166198
this._onCentered.emit();
@@ -173,7 +205,7 @@ export class MatTabBody implements OnInit, AfterViewChecked {
173205
}
174206

175207
/** Whether the provided position state is considered center, regardless of origin. */
176-
private _isCenterPosition(position: MatTabBodyPositionState|string): boolean {
208+
_isCenterPosition(position: MatTabBodyPositionState|string): boolean {
177209
return position == 'center' ||
178210
position == 'left-origin-center' ||
179211
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('MatTabGroup', () => {
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('MatTabGroup', () => {
322329
fixture.debugElement.query(By.directive(MatTabGroup)).componentInstance as MatTabGroup;
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 MatTabGroup with enabled animations', () => {
419429
</mat-tab>
420430
<mat-tab>
421431
<ng-template mat-tab-label>Tab Two</ng-template>
422-
Tab two content
432+
<span>Tab </span><span>two</span><span>content</span>
423433
</mat-tab>
424434
<mat-tab>
425435
<ng-template mat-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 {MatCommonModule, MatRippleModule} from '@angular/material/core';
1515
import {MatInkBar} from './ink-bar';
1616
import {MatTab} from './tab';
17-
import {MatTabBody} from './tab-body';
17+
import {MatTabBody, MatTabBodyPortal} from './tab-body';
1818
import {MatTabGroup} from './tab-group';
1919
import {MatTabHeader} from './tab-header';
2020
import {MatTabLabel} from './tab-label';
@@ -49,6 +49,7 @@ import {MatTabLink, MatTabNav} from './tab-nav-bar/tab-nav-bar';
4949
MatTabNav,
5050
MatTabLink,
5151
MatTabBody,
52+
MatTabBodyPortal,
5253
MatTabHeader
5354
],
5455
providers: [VIEWPORT_RULER_PROVIDER],

0 commit comments

Comments
 (0)