Skip to content

Commit ea6c817

Browse files
authored
feat(tabs): simplify api (#1645)
1 parent 04e2201 commit ea6c817

File tree

8 files changed

+174
-74
lines changed

8 files changed

+174
-74
lines changed

src/demo-app/tabs/tabs-demo.html

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,11 @@ <h1>Tab Group Demo</h1>
1919
<md-tab-group class="demo-tab-group">
2020
<md-tab *ngFor="let tab of tabs; let i = index" [disabled]="i == 1">
2121
<template md-tab-label>{{tab.label}}</template>
22-
<template md-tab-content>
23-
{{tab.content}}
24-
<br>
25-
<br>
26-
<br>
27-
<md-input placeholder="Tab Label" [(ngModel)]="tab.label"></md-input>
28-
</template>
22+
{{tab.content}}
23+
<br>
24+
<br>
25+
<br>
26+
<md-input placeholder="Tab Label" [(ngModel)]="tab.label"></md-input>
2927
</md-tab>
3028
</md-tab-group>
3129

@@ -35,12 +33,21 @@ <h1>Async Tabs</h1>
3533
<md-tab-group class="demo-tab-group">
3634
<md-tab *ngFor="let tab of asyncTabs | async; let i = index" [disabled]="i == 1">
3735
<template md-tab-label>{{tab.label}}</template>
38-
<template md-tab-content>
39-
{{tab.content}}
40-
<br>
41-
<br>
42-
<br>
43-
<md-input placeholder="Tab Label" [(ngModel)]="tab.label"></md-input>
44-
</template>
36+
{{tab.content}}
37+
<br>
38+
<br>
39+
<br>
40+
<md-input placeholder="Tab Label" [(ngModel)]="tab.label"></md-input>
41+
</md-tab>
42+
</md-tab-group>
43+
44+
<!-- Simple tabs api -->
45+
<h1>Tabs with simplified api</h1>
46+
<md-tab-group class="demo-tab-group">
47+
<md-tab label="Earth">
48+
This tab is about the Earth!
49+
</md-tab>
50+
<md-tab label="Fire">
51+
This tab is about combustion!
4552
</md-tab>
4653
</md-tab-group>

src/demo-app/tabs/tabs-demo.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@ import {Observable} from 'rxjs/Observable';
1111
})
1212
export class TabsDemo {
1313
tabLinks = [
14-
{ label: 'Sun', link: 'sunny-tab'},
15-
{ label: 'Rain', link: 'rainy-tab'},
16-
{ label: 'Fog', link: 'foggy-tab'},
14+
{label: 'Sun', link: 'sunny-tab'},
15+
{label: 'Rain', link: 'rainy-tab'},
16+
{label: 'Fog', link: 'foggy-tab'},
1717
];
1818
activeLinkIndex = 0;
1919

2020
tabs = [
21-
{ label: 'Tab One', content: 'This is the body of the first tab' },
22-
{ label: 'Tab Two', content: 'This is the body of the second tab' },
23-
{ label: 'Tab Three', content: 'This is the body of the third tab' },
21+
{label: 'Tab One', content: 'This is the body of the first tab'},
22+
{label: 'Tab Two', content: 'This is the body of the second tab'},
23+
{label: 'Tab Three', content: 'This is the body of the third tab'},
2424
];
2525

2626
asyncTabs: Observable<any>;

src/lib/tabs/README.md

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,22 @@ Tab groups allow the user to organize their content by labels such that only one
1515
| `focusChange` | `Event` | Fired when focus changes from one label to another |
1616
| `selectChange` | `Event` | Fired when the selected tab changes |
1717

18-
### Examples
18+
### Basic use
1919
A basic tab group would have the following markup.
2020
```html
2121
<md-tab-group>
22-
<md-tab>
23-
<template md-tab-label>One</template>
24-
<template md-tab-content>
25-
<h1>Some tab content</h1>
26-
<p>...</p>
27-
</template>
22+
<md-tab label="One">
23+
<h1>Some tab content</h1>
24+
<p>...</p>
2825
</md-tab>
29-
<md-tab>
30-
<template md-tab-label>Two</template>
31-
<template md-tab-content>
32-
<h1>Some more tab content</h1>
33-
<p>...</p>
34-
</template>
26+
<md-tab label="Two">
27+
<h1>Some more tab content</h1>
28+
<p>...</p>
3529
</md-tab>
3630
</md-tab-group>
3731
```
3832

39-
It is also possible to specifiy the active tab by using the `selectedIndex` property.
33+
You can specifiy the active tab by using the `selectedIndex` property.
4034

4135
```html
4236
<md-tab-group [selectedIndex]="1">
@@ -45,3 +39,26 @@ It is also possible to specifiy the active tab by using the `selectedIndex` prop
4539
```
4640

4741
**Note**: The index always starts counting from `zero`.
42+
43+
44+
### Tabs with label templates
45+
If you want to use an arbitrary template for your tab, you can use the `md-tab-label` directive to
46+
provide the label template:
47+
```html
48+
<md-tab-group>
49+
<md-tab>
50+
<template md-tab-label>
51+
The <em>best</em> pasta
52+
</template>
53+
<h1>Best pasta restaurants</h1>
54+
<p>...</p>
55+
</md-tab>
56+
<md-tab>
57+
<template md-tab-label>
58+
<md-icon>thumb_down</md-icon> The worst sushi
59+
</template>
60+
<h1>Terrible sushi restaurants</h1>
61+
<p>...</p>
62+
</md-tab>
63+
</md-tab-group>
64+
```

src/lib/tabs/tab-content.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/lib/tabs/tab-group.html

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@
99
[class.md-tab-active]="selectedIndex == i"
1010
[class.md-tab-disabled]="tab.disabled"
1111
(click)="focusIndex = selectedIndex = i">
12-
<template [portalHost]="tab.label"></template>
12+
13+
<!-- If there is a label template, use it. -->
14+
<template [ngIf]="tab.templateLabel">
15+
<template [portalHost]="tab.templateLabel"></template>
16+
</template>
17+
18+
<!-- If there is not a label template, fall back to the text label. -->
19+
<template [ngIf]="!tab.templateLabel">{{tab.textLabel}}</template>
1320
</div>
1421
<md-ink-bar></md-ink-bar>
1522
</div>

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

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ describe('MdTabGroup', () => {
1313
declarations: [
1414
SimpleTabsTestApp,
1515
AsyncTabsTestApp,
16-
DisabledTabsTestApp
16+
DisabledTabsTestApp,
17+
TabGroupWithSimpleApi,
1718
],
1819
});
1920

@@ -241,6 +242,37 @@ describe('MdTabGroup', () => {
241242
}));
242243
});
243244

245+
describe('with simple api', () => {
246+
let fixture: ComponentFixture<TabGroupWithSimpleApi>;
247+
let tabGroup: MdTabGroup;
248+
249+
beforeEach(() => {
250+
fixture = TestBed.createComponent(TabGroupWithSimpleApi);
251+
fixture.detectChanges();
252+
253+
tabGroup =
254+
fixture.debugElement.query(By.directive(MdTabGroup)).componentInstance as MdTabGroup;
255+
});
256+
257+
it('should support a tab-group with the simple api', () => {
258+
expect(getSelectedLabel(fixture).textContent).toMatch('Junk food');
259+
expect(getSelectedContent(fixture).textContent).toMatch('Pizza, fries');
260+
261+
tabGroup.selectedIndex = 2;
262+
fixture.detectChanges();
263+
264+
expect(getSelectedLabel(fixture).textContent).toMatch('Fruit');
265+
expect(getSelectedContent(fixture).textContent).toMatch('Apples, grapes');
266+
267+
fixture.componentInstance.otherLabel = 'Chips';
268+
fixture.componentInstance.otherContent = 'Salt, vinegar';
269+
fixture.detectChanges();
270+
271+
expect(getSelectedLabel(fixture).textContent).toMatch('Chips');
272+
expect(getSelectedContent(fixture).textContent).toMatch('Salt, vinegar');
273+
});
274+
});
275+
244276
/**
245277
* Checks that the `selectedIndex` has been updated; checks that the label and body have the
246278
* `md-tab-active` class
@@ -260,26 +292,33 @@ describe('MdTabGroup', () => {
260292
.query(By.css(`#${tabLabelElement.id}`)).nativeElement;
261293
expect(tabContentElement.classList.contains('md-tab-active')).toBe(true);
262294
}
295+
296+
function getSelectedLabel(fixture: ComponentFixture<any>): HTMLElement {
297+
return fixture.nativeElement.querySelector('.md-tab-label.md-tab-active');
298+
}
299+
300+
function getSelectedContent(fixture: ComponentFixture<any>): HTMLElement {
301+
return fixture.nativeElement.querySelector('.md-tab-body.md-tab-active');
302+
}
263303
});
264304

265305
@Component({
266-
selector: 'test-app',
267306
template: `
268307
<md-tab-group class="tab-group"
269308
[(selectedIndex)]="selectedIndex"
270309
(focusChange)="handleFocus($event)"
271310
(selectChange)="handleSelection($event)">
272311
<md-tab>
273312
<template md-tab-label>Tab One</template>
274-
<template md-tab-content>Tab one content</template>
313+
Tab one content
275314
</md-tab>
276315
<md-tab>
277316
<template md-tab-label>Tab Two</template>
278-
<template md-tab-content>Tab two content</template>
317+
Tab two content
279318
</md-tab>
280319
<md-tab>
281320
<template md-tab-label>Tab Three</template>
282-
<template md-tab-content>Tab three content</template>
321+
Tab three content
283322
</md-tab>
284323
</md-tab-group>
285324
`
@@ -302,28 +341,27 @@ class SimpleTabsTestApp {
302341
<md-tab-group class="tab-group">
303342
<md-tab>
304343
<template md-tab-label>Tab One</template>
305-
<template md-tab-content>Tab one content</template>
344+
Tab one content
306345
</md-tab>
307346
<md-tab disabled>
308347
<template md-tab-label>Tab Two</template>
309-
<template md-tab-content>Tab two content</template>
348+
Tab two content
310349
</md-tab>
311350
<md-tab>
312351
<template md-tab-label>Tab Three</template>
313-
<template md-tab-content>Tab three content</template>
352+
Tab three content
314353
</md-tab>
315354
</md-tab-group>
316355
`,
317356
})
318357
class DisabledTabsTestApp {}
319358

320359
@Component({
321-
selector: 'test-app',
322360
template: `
323361
<md-tab-group class="tab-group">
324362
<md-tab *ngFor="let tab of tabs | async">
325363
<template md-tab-label>{{ tab.label }}</template>
326-
<template md-tab-content>{{ tab.content }}</template>
364+
{{ tab.content }}
327365
</md-tab>
328366
</md-tab-group>
329367
`
@@ -343,3 +381,18 @@ class AsyncTabsTestApp {
343381
});
344382
}
345383
}
384+
385+
386+
@Component({
387+
template: `
388+
<md-tab-group>
389+
<md-tab label="Junk food"> Pizza, fries </md-tab>
390+
<md-tab label="Vegetables"> Broccoli, spinach </md-tab>
391+
<md-tab [label]="otherLabel"> {{otherContent}} </md-tab>
392+
</md-tab-group>
393+
`
394+
})
395+
class TabGroupWithSimpleApi {
396+
otherLabel = 'Fruit';
397+
otherContent = 'Apples, grapes';
398+
}

src/lib/tabs/tab.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<!-- Create a template for the content of the <md-tab> so that we can grab a reference to this
2+
TemplateRef and use it in a Portal to render the tab content in the appropriate place in the
3+
tab-group. -->
4+
<template><ng-content></ng-content></template>

src/lib/tabs/tabs.ts

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,29 @@ import {
22
NgModule,
33
ModuleWithProviders,
44
ContentChild,
5-
Directive,
5+
ViewChild,
66
Component,
77
Input,
88
Output,
99
ViewChildren,
1010
NgZone,
1111
EventEmitter,
1212
QueryList,
13-
ContentChildren
13+
ContentChildren,
14+
TemplateRef,
15+
ViewContainerRef,
16+
OnInit,
1417
} from '@angular/core';
1518
import {CommonModule} from '@angular/common';
16-
import {PortalModule, RIGHT_ARROW, LEFT_ARROW, ENTER, coerceBooleanProperty} from '../core';
19+
import {
20+
PortalModule,
21+
TemplatePortal,
22+
RIGHT_ARROW,
23+
LEFT_ARROW,
24+
ENTER,
25+
coerceBooleanProperty,
26+
} from '../core';
1727
import {MdTabLabel} from './tab-label';
18-
import {MdTabContent} from './tab-content';
1928
import {MdTabLabelWrapper} from './tab-label-wrapper';
2029
import {MdTabNavBar, MdTabLink} from './tab-nav-bar/tab-nav-bar';
2130
import {MdInkBar} from './ink-bar';
@@ -32,20 +41,35 @@ export class MdTabChangeEvent {
3241
tab: MdTab;
3342
}
3443

35-
@Directive({
36-
selector: 'md-tab'
44+
@Component({
45+
moduleId: module.id,
46+
selector: 'md-tab',
47+
templateUrl: 'tab.html',
3748
})
38-
export class MdTab {
39-
@ContentChild(MdTabLabel) label: MdTabLabel;
40-
@ContentChild(MdTabContent) content: MdTabContent;
49+
export class MdTab implements OnInit {
50+
/** Content for the tab label given by <template md-tab-label>. */
51+
@ContentChild(MdTabLabel) templateLabel: MdTabLabel;
4152

42-
private _disabled = false;
43-
@Input('disabled')
44-
set disabled(value: boolean) {
45-
this._disabled = coerceBooleanProperty(value);
53+
/** Template inside the MdTab view that contains an <ng-content>. */
54+
@ViewChild(TemplateRef) _content: TemplateRef<any>;
55+
56+
/** The plain text label for the tab, used when there is no template label. */
57+
@Input('label') textLabel: string = '';
58+
59+
private _contentPortal: TemplatePortal = null;
60+
61+
constructor(private _viewContainerRef: ViewContainerRef) { }
62+
63+
ngOnInit() {
64+
this._contentPortal = new TemplatePortal(this._content, this._viewContainerRef);
4665
}
47-
get disabled(): boolean {
48-
return this._disabled;
66+
67+
private _disabled = false;
68+
@Input() set disabled(value: boolean) { this._disabled = coerceBooleanProperty(value); }
69+
get disabled(): boolean { return this._disabled; }
70+
71+
get content(): TemplatePortal {
72+
return this._contentPortal;
4973
}
5074
}
5175

@@ -230,8 +254,8 @@ export class MdTabGroup {
230254
@NgModule({
231255
imports: [CommonModule, PortalModule],
232256
// Don't export MdInkBar or MdTabLabelWrapper, as they are internal implementation details.
233-
exports: [MdTabGroup, MdTabLabel, MdTabContent, MdTab, MdTabNavBar, MdTabLink],
234-
declarations: [MdTabGroup, MdTabLabel, MdTabContent, MdTab, MdInkBar, MdTabLabelWrapper,
257+
exports: [MdTabGroup, MdTabLabel, MdTab, MdTabNavBar, MdTabLink],
258+
declarations: [MdTabGroup, MdTabLabel, MdTab, MdInkBar, MdTabLabelWrapper,
235259
MdTabNavBar, MdTabLink],
236260
})
237261
export class MdTabsModule {

0 commit comments

Comments
 (0)