Skip to content

Commit 6520e9b

Browse files
committed
fix(toolbar): don't auto-generate first row if first row would be empty
Currently the toolbar always generates the first `<md-toolbar-row>`. This means that developers have no opportunity to set directives/attributes/classes on the first toolbar row (e.g with flex-layout). With this change, the first toolbar row will be only auto-generated if there is user content that can be projected into the default first row. This means that developers can now do the following: ```html <md-toolbar> <md-toolbar-row>First Row</md-toolbar-row> <md-toolbar-row>Second Row</md-toolbar-row> </md-toolbar> ``` Fixes #6004. Fixes #1718.
1 parent ec4ea06 commit 6520e9b

File tree

5 files changed

+104
-39
lines changed

5 files changed

+104
-39
lines changed

src/lib/toolbar/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77
*/
88

99
import {NgModule} from '@angular/core';
10+
import {PlatformModule} from '@angular/cdk/platform';
1011
import {MdCommonModule} from '../core';
1112
import {MdToolbar, MdToolbarRow} from './toolbar';
1213

1314

1415
@NgModule({
15-
imports: [MdCommonModule],
16+
imports: [MdCommonModule, PlatformModule],
1617
exports: [MdToolbar, MdToolbarRow, MdCommonModule],
1718
declarations: [MdToolbar, MdToolbarRow],
1819
})

src/lib/toolbar/toolbar.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div class="mat-toolbar-layout">
2-
<md-toolbar-row>
2+
<md-toolbar-row #defaultFirstRow>
33
<ng-content></ng-content>
44
</md-toolbar-row>
55
<ng-content select="md-toolbar-row, mat-toolbar-row"></ng-content>

src/lib/toolbar/toolbar.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ Each toolbar row is a `display: flex` container.
2121
</md-toolbar>
2222
```
2323

24+
In some situations, developers need full control over all `<md-toolbar-row>` elements
25+
(e.g. for use with flex-layout).
26+
27+
To have full control over the first `<md-toolbar-row>`, which is usually auto-generated,
28+
there should be only row elements in the `<md-toolbar>`.
29+
30+
```html
31+
<md-toolbar>
32+
<md-toolbar-row>First Row</md-toolbar-row>
33+
<md-toolbar-row>Second Row</md-toolbar-row>
34+
</md-toolbar>
35+
```
36+
2437
### Positioning toolbar content
2538
The toolbar does not perform any positioning of its content. This gives the user full power to
2639
position the content as it suits their application.

src/lib/toolbar/toolbar.spec.ts

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,55 +3,86 @@ import {TestBed, async, ComponentFixture} from '@angular/core/testing';
33
import {By} from '@angular/platform-browser';
44
import {MdToolbarModule} from './index';
55

6-
76
describe('MdToolbar', () => {
87

9-
let fixture: ComponentFixture<TestApp>;
10-
let testComponent: TestApp;
11-
let toolbarElement: HTMLElement;
12-
138
beforeEach(async(() => {
149
TestBed.configureTestingModule({
1510
imports: [MdToolbarModule],
16-
declarations: [TestApp],
11+
declarations: [ToolbarWithDefaultFirstRow, ToolbarWithoutDefaultFirstRow],
1712
});
1813

1914
TestBed.compileComponents();
2015
}));
2116

22-
beforeEach(() => {
23-
fixture = TestBed.createComponent(TestApp);
24-
testComponent = fixture.debugElement.componentInstance;
25-
toolbarElement = fixture.debugElement.query(By.css('md-toolbar')).nativeElement;
26-
});
17+
describe('with default first row', () => {
18+
let fixture: ComponentFixture<ToolbarWithDefaultFirstRow>;
19+
let testComponent: ToolbarWithDefaultFirstRow;
20+
let toolbarElement: HTMLElement;
21+
let toolbarRowElements: HTMLElement[];
22+
23+
beforeEach(() => {
24+
fixture = TestBed.createComponent(ToolbarWithDefaultFirstRow);
25+
testComponent = fixture.debugElement.componentInstance;
26+
toolbarElement = fixture.debugElement.query(By.css('.mat-toolbar')).nativeElement;
27+
toolbarRowElements = fixture.debugElement.queryAll(By.css('.mat-toolbar-row'))
28+
.map(rowDebugElement => rowDebugElement.nativeElement);
29+
});
2730

28-
it('should apply class based on color attribute', () => {
29-
testComponent.toolbarColor = 'primary';
30-
fixture.detectChanges();
31+
it('should apply class based on color attribute', () => {
32+
testComponent.toolbarColor = 'primary';
33+
fixture.detectChanges();
3134

32-
expect(toolbarElement.classList.contains('mat-primary')).toBe(true);
35+
expect(toolbarElement.classList.contains('mat-primary')).toBe(true);
3336

34-
testComponent.toolbarColor = 'accent';
35-
fixture.detectChanges();
37+
testComponent.toolbarColor = 'accent';
38+
fixture.detectChanges();
3639

37-
expect(toolbarElement.classList.contains('mat-primary')).toBe(false);
38-
expect(toolbarElement.classList.contains('mat-accent')).toBe(true);
40+
expect(toolbarElement.classList.contains('mat-primary')).toBe(false);
41+
expect(toolbarElement.classList.contains('mat-accent')).toBe(true);
42+
43+
testComponent.toolbarColor = 'warn';
44+
fixture.detectChanges();
45+
46+
expect(toolbarElement.classList.contains('mat-accent')).toBe(false);
47+
expect(toolbarElement.classList.contains('mat-warn')).toBe(true);
48+
});
3949

40-
testComponent.toolbarColor = 'warn';
41-
fixture.detectChanges();
50+
it('should set the toolbar role on the host', () => {
51+
expect(toolbarElement.getAttribute('role')).toBe('toolbar');
52+
});
4253

43-
expect(toolbarElement.classList.contains('mat-accent')).toBe(false);
44-
expect(toolbarElement.classList.contains('mat-warn')).toBe(true);
54+
it('should generate a default first row if content is projected', () => {
55+
expect(toolbarRowElements.length)
56+
.toBe(2, 'Expected a default first row and a second row to be present.');
57+
});
4558
});
4659

47-
it('should set the toolbar role on the host', () => {
48-
expect(toolbarElement.getAttribute('role')).toBe('toolbar');
60+
describe('without default first row', () => {
61+
62+
it('should not generate a default first row if no content is projected', () => {
63+
const fixture = TestBed.createComponent(ToolbarWithoutDefaultFirstRow);
64+
65+
expect(fixture.debugElement.queryAll(By.css('.mat-toolbar-row')).length)
66+
.toBe(2, 'Expected one toolbar row to be present while no content is projected.');
67+
});
4968
});
5069

70+
5171
});
5272

5373

54-
@Component({template: `<md-toolbar [color]="toolbarColor">Test Toolbar</md-toolbar>`})
55-
class TestApp {
74+
@Component({template: `
75+
<md-toolbar [color]="toolbarColor">
76+
First Row
77+
<md-toolbar-row>Second Row</md-toolbar-row>
78+
</md-toolbar>`})
79+
class ToolbarWithDefaultFirstRow {
5680
toolbarColor: string;
5781
}
82+
83+
@Component({template: `
84+
<md-toolbar [color]="toolbarColor">
85+
<md-toolbar-row>First Row</md-toolbar-row>
86+
</md-toolbar>
87+
`})
88+
class ToolbarWithoutDefaultFirstRow {}

src/lib/toolbar/toolbar.ts

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,11 @@ import {
1313
Directive,
1414
ElementRef,
1515
Renderer2,
16+
ViewChild,
17+
AfterViewInit,
1618
} from '@angular/core';
1719
import {CanColor, mixinColor} from '../core/common-behaviors/color';
18-
19-
20-
@Directive({
21-
selector: 'md-toolbar-row, mat-toolbar-row',
22-
host: {'class': 'mat-toolbar-row'},
23-
})
24-
export class MdToolbarRow {}
20+
import {Platform} from '@angular/cdk/platform';
2521

2622
// Boilerplate for applying mixins to MdToolbar.
2723
/** @docs-private */
@@ -30,7 +26,6 @@ export class MdToolbarBase {
3026
}
3127
export const _MdToolbarMixinBase = mixinColor(MdToolbarBase);
3228

33-
3429
@Component({
3530
moduleId: module.id,
3631
selector: 'md-toolbar, mat-toolbar',
@@ -44,10 +39,35 @@ export const _MdToolbarMixinBase = mixinColor(MdToolbarBase);
4439
changeDetection: ChangeDetectionStrategy.OnPush,
4540
encapsulation: ViewEncapsulation.None
4641
})
47-
export class MdToolbar extends _MdToolbarMixinBase implements CanColor {
42+
export class MdToolbar extends _MdToolbarMixinBase implements CanColor, AfterViewInit {
4843

49-
constructor(renderer: Renderer2, elementRef: ElementRef) {
44+
/** Element reference to the default first row of the toolbar. */
45+
@ViewChild('defaultFirstRow', { read: ElementRef }) _defaultFirstRow: ElementRef;
46+
47+
constructor(renderer: Renderer2, elementRef: ElementRef, private _platform: Platform) {
5048
super(renderer, elementRef);
5149
}
5250

51+
ngAfterViewInit() {
52+
// Do nothing if we're not on the browser platform.
53+
if (!this._platform.isBrowser) {
54+
return;
55+
}
56+
57+
const firstRowElement = this._defaultFirstRow.nativeElement;
58+
59+
// If the first toolbar row, which will be auto-generated, does not have any projected content,
60+
// then the auto-generated first toolbar row should be removed. This gives developers
61+
// full control over the first toolbar row (e.g. for use with flex-layout).
62+
if (!firstRowElement.innerHTML.trim()) {
63+
firstRowElement.parentNode.removeChild(firstRowElement);
64+
}
65+
}
5366
}
67+
68+
@Directive({
69+
selector: 'md-toolbar-row, mat-toolbar-row',
70+
host: {'class': 'mat-toolbar-row'},
71+
})
72+
export class MdToolbarRow {}
73+

0 commit comments

Comments
 (0)