Skip to content

Commit c79ec26

Browse files
julliermecrisbeto
authored andcommitted
fix(material/form-field): hiding a label after it has been (#29461)
shown leaves a blank space There is a method _shouldLabelFloat that determines if the label should float. A check was added `_hasFloatingLabel` to see if a floating label exists before deciding whether the label should float. Examples were added at the end of the input-demo file, where you can see inputs without labels (both fixed and dynamic). Removing the solution also allows you to simulate the described error. Unit tests were added to validate the solution. Fixes #29401 (cherry picked from commit 13aef8c)
1 parent 15238d2 commit c79ec26

File tree

4 files changed

+126
-3
lines changed

4 files changed

+126
-3
lines changed

src/dev-app/input/input-demo.html

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,41 @@ <h4>Custom control</h4>
880880
</mat-card-content>
881881
</mat-card>
882882

883+
<mat-card class="demo-card demo-basic">
884+
<mat-toolbar color="primary">Without label</mat-toolbar>
885+
<mat-card-content>
886+
<h4>Label removed</h4>
887+
<p>
888+
<mat-form-field appearance="outline">
889+
@if (hasLabel$ | async){
890+
<mat-label>My input</mat-label>
891+
}
892+
<input matInput type="text" />
893+
</mat-form-field>
894+
</p>
895+
<p>
896+
<mat-form-field>
897+
@if (hasLabel$ | async){
898+
<mat-label>My input</mat-label>
899+
}
900+
<input matInput type="text" />
901+
</mat-form-field>
902+
</p>
903+
<h4>No defined label</h4>
904+
<p>
905+
<mat-form-field appearance="outline">
906+
<input matInput type="text"/>
907+
</mat-form-field>
908+
</p>
909+
<p>
910+
<mat-form-field>
911+
<input matInput type="text"/>
912+
</mat-form-field>
913+
</p>
914+
</mat-card-content>
915+
</mat-card>
916+
917+
883918
<mat-card class="demo-card demo-basic">
884919
<mat-card-content>
885920
<button (click)="showHidden = !showHidden">Show/hide hidden form-field</button>

src/dev-app/input/input-demo.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {MatIconModule} from '@angular/material/icon';
2929
import {MatTabsModule} from '@angular/material/tabs';
3030
import {MatToolbarModule} from '@angular/material/toolbar';
3131
import {MatTooltipModule} from '@angular/material/tooltip';
32+
import {BehaviorSubject} from 'rxjs';
3233

3334
let max = 5;
3435

@@ -100,8 +101,13 @@ export class InputDemo {
100101
fillAppearance: string;
101102
outlineAppearance: string;
102103

104+
hasLabel$ = new BehaviorSubject(true);
105+
103106
constructor() {
104-
setTimeout(() => this.delayedFormControl.setValue('hello'), 100);
107+
setTimeout(() => {
108+
this.delayedFormControl.setValue('hello');
109+
this.hasLabel$.next(false);
110+
}, 100);
105111
}
106112

107113
addABunch(n: number) {

src/material/form-field/form-field.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,10 @@ export class MatFormField
555555

556556
_hasFloatingLabel = computed(() => !!this._labelChild());
557557

558-
_shouldLabelFloat() {
558+
_shouldLabelFloat(): boolean {
559+
if (!this._hasFloatingLabel()) {
560+
return false;
561+
}
559562
return this._control.shouldLabelFloat || this._shouldAlwaysFloat();
560563
}
561564

src/material/input/input.spec.ts

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,8 @@ describe('MatMdcInput without forms', () => {
157157
fixture.detectChanges();
158158

159159
expect(formField._control.empty).toBe(false);
160-
expect(formField._shouldLabelFloat()).toBe(true);
160+
// should not float label if there is no label
161+
expect(formField._shouldLabelFloat()).toBe(false);
161162
}));
162163

163164
it('should not be empty when the value set before view init', fakeAsync(() => {
@@ -1531,6 +1532,62 @@ describe('MatFormField default options', () => {
15311532
).toBe(true);
15321533
});
15331534
});
1535+
describe('MatFormField without label', () => {
1536+
it('should not float the label when no label is defined.', () => {
1537+
let fixture = createComponent(MatInputWithoutDefinedLabel);
1538+
fixture.detectChanges();
1539+
1540+
const inputEl = fixture.debugElement.query(By.css('input'))!;
1541+
const formField = fixture.debugElement.query(By.directive(MatFormField))!
1542+
.componentInstance as MatFormField;
1543+
1544+
// Update the value of the input and set focus.
1545+
inputEl.nativeElement.value = 'Text';
1546+
fixture.detectChanges();
1547+
1548+
// should not float label if there is no label
1549+
expect(formField._shouldLabelFloat()).toBe(false);
1550+
});
1551+
1552+
it('should not float the label when the label is removed after it has been shown', () => {
1553+
let fixture = createComponent(MatInputWithCondictionalLabel);
1554+
fixture.detectChanges();
1555+
const inputEl = fixture.debugElement.query(By.css('input'))!;
1556+
const formField = fixture.debugElement.query(By.directive(MatFormField))!
1557+
.componentInstance as MatFormField;
1558+
1559+
// initially, label is present
1560+
expect(fixture.nativeElement.querySelector('label')).not.toBeNull();
1561+
1562+
// removing label after it has been shown
1563+
fixture.componentInstance.hasLabel = false;
1564+
inputEl.nativeElement.value = 'Text';
1565+
fixture.changeDetectorRef.markForCheck();
1566+
fixture.detectChanges();
1567+
1568+
// now expected to not have a label
1569+
expect(fixture.nativeElement.querySelector('label')).toBeNull();
1570+
// should not float label since there is no label
1571+
expect(formField._shouldLabelFloat()).toBe(false);
1572+
});
1573+
1574+
it('should float the label when the label is not removed', () => {
1575+
let fixture = createComponent(MatInputWithCondictionalLabel);
1576+
fixture.detectChanges();
1577+
const inputEl = fixture.debugElement.query(By.css('input'))!;
1578+
const formField = fixture.debugElement.query(By.directive(MatFormField))!
1579+
.componentInstance as MatFormField;
1580+
1581+
inputEl.nativeElement.value = 'Text';
1582+
fixture.changeDetectorRef.markForCheck();
1583+
fixture.detectChanges();
1584+
1585+
// Expected to have a label
1586+
expect(fixture.nativeElement.querySelector('label')).not.toBeNull();
1587+
// should float label since there is a label
1588+
expect(formField._shouldLabelFloat()).toBe(true);
1589+
});
1590+
});
15341591

15351592
function configureTestingModule(
15361593
component: Type<any>,
@@ -1787,6 +1844,28 @@ class MatInputWithDynamicLabel {
17871844
shouldFloat: 'always' | 'auto' = 'always';
17881845
}
17891846

1847+
@Component({
1848+
template: `
1849+
<mat-form-field>
1850+
<input matInput placeholder="Label">
1851+
</mat-form-field>
1852+
`,
1853+
})
1854+
class MatInputWithoutDefinedLabel {}
1855+
1856+
@Component({
1857+
template: `
1858+
<mat-form-field>
1859+
@if (hasLabel) {
1860+
<mat-label>Label</mat-label>
1861+
}
1862+
<input matInput>
1863+
</mat-form-field>`,
1864+
})
1865+
class MatInputWithCondictionalLabel {
1866+
hasLabel = true;
1867+
}
1868+
17901869
@Component({
17911870
template: `
17921871
<mat-form-field>

0 commit comments

Comments
 (0)