Skip to content

Commit 632f238

Browse files
committed
fix(material/form-field): add hasFloatingLabel input and update classes if mat-label is added and removed dynamically
Currently, when `mat-Label` is added dynamically initially its not visible in DOM, this fix will add/remove classes for the same. Fixes #29939
1 parent ca73b56 commit 632f238

File tree

5 files changed

+82
-14
lines changed

5 files changed

+82
-14
lines changed

goldens/material/form-field/index.api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ import * as i2 from '@angular/cdk/observers';
1616
import { InjectionToken } from '@angular/core';
1717
import { NgControl } from '@angular/forms';
1818
import { Observable } from 'rxjs';
19+
import { OnChanges } from '@angular/core';
1920
import { OnDestroy } from '@angular/core';
2021
import { QueryList } from '@angular/core';
22+
import { SimpleChanges } from '@angular/core';
2123

2224
// @public
2325
export type FloatLabelType = 'always' | 'auto';

goldens/material/input/index.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { OnChanges } from '@angular/core';
2525
import { OnDestroy } from '@angular/core';
2626
import { Platform } from '@angular/cdk/platform';
2727
import { QueryList } from '@angular/core';
28+
import { SimpleChanges } from '@angular/core';
2829
import { Subject } from 'rxjs';
2930
import { WritableSignal } from '@angular/core';
3031

src/material/form-field/directives/notched-outline.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
ElementRef,
1414
Input,
1515
NgZone,
16+
OnChanges,
17+
SimpleChanges,
1618
ViewChild,
1719
ViewEncapsulation,
1820
inject,
@@ -36,21 +38,30 @@ import {
3638
changeDetection: ChangeDetectionStrategy.OnPush,
3739
encapsulation: ViewEncapsulation.None,
3840
})
39-
export class MatFormFieldNotchedOutline implements AfterViewInit {
41+
export class MatFormFieldNotchedOutline implements AfterViewInit, OnChanges {
4042
private _elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
4143
private _ngZone = inject(NgZone);
4244

4345
/** Whether the notch should be opened. */
4446
@Input('matFormFieldNotchedOutlineOpen') open: boolean = false;
4547

48+
/** Whether the floating label is present. */
49+
@Input('matFormFieldHasFloatingLabel') hasFloatingLabel: boolean = false;
50+
4651
@ViewChild('notch') _notch: ElementRef<HTMLElement>;
4752

48-
ngAfterViewInit(): void {
49-
const element = this._elementRef.nativeElement;
50-
const label = element.querySelector<HTMLElement>('.mdc-floating-label');
53+
/** Gets the HTML element for the floating label. */
54+
get element(): HTMLElement {
55+
return this._elementRef.nativeElement;
56+
}
57+
58+
constructor(...args: unknown[]);
59+
constructor() {}
5160

61+
ngAfterViewInit(): void {
62+
const label = this.element.querySelector<HTMLElement>('.mdc-floating-label');
5263
if (label) {
53-
element.classList.add('mdc-notched-outline--upgraded');
64+
this.element.classList.add('mdc-notched-outline--upgraded');
5465

5566
if (typeof requestAnimationFrame === 'function') {
5667
label.style.transitionDuration = '0s';
@@ -59,7 +70,18 @@ export class MatFormFieldNotchedOutline implements AfterViewInit {
5970
});
6071
}
6172
} else {
62-
element.classList.add('mdc-notched-outline--no-label');
73+
this.element.classList.add('mdc-notched-outline--no-label');
74+
}
75+
}
76+
77+
ngOnChanges(changes: SimpleChanges) {
78+
if (
79+
changes['hasFloatingLabel'] &&
80+
this.hasFloatingLabel &&
81+
this.element.classList.contains('mdc-notched-outline--no-label')
82+
) {
83+
this.element.classList.add('mdc-notched-outline--upgraded');
84+
this.element.classList.remove('mdc-notched-outline--no-label');
6385
}
6486
}
6587

src/material/form-field/form-field.html

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@
5050
}
5151
<div class="mat-mdc-form-field-flex">
5252
@if (_hasOutline()) {
53-
<div matFormFieldNotchedOutline [matFormFieldNotchedOutlineOpen]="_shouldLabelFloat()">
53+
<div
54+
matFormFieldNotchedOutline
55+
[matFormFieldNotchedOutlineOpen]="_shouldLabelFloat()"
56+
[matFormFieldHasFloatingLabel]="_hasFloatingLabel()">
5457
@if (!_forceDisplayInfixLabel()) {
5558
<ng-template [ngTemplateOutlet]="labelTemplate"></ng-template>
5659
}
@@ -96,20 +99,20 @@
9699
</div>
97100

98101
<div
99-
class="mat-mdc-form-field-subscript-wrapper mat-mdc-form-field-bottom-align"
100-
[class.mat-mdc-form-field-subscript-dynamic-size]="subscriptSizing === 'dynamic'"
101-
>
102+
class="mat-mdc-form-field-subscript-wrapper mat-mdc-form-field-bottom-align"
103+
[class.mat-mdc-form-field-subscript-dynamic-size]="subscriptSizing === 'dynamic'">
102104
@let subscriptMessageType = _getSubscriptMessageType();
103105

104106
<!--
105107
Use a single permanent wrapper for both hints and errors so aria-live works correctly,
106108
as having it appear post render will not consistently work. We also do not want to add
107109
additional divs as it causes styling regressions.
108110
-->
109-
<div aria-atomic="true" aria-live="polite"
110-
[class.mat-mdc-form-field-error-wrapper]="subscriptMessageType === 'error'"
111-
[class.mat-mdc-form-field-hint-wrapper]="subscriptMessageType === 'hint'"
112-
>
111+
<div
112+
aria-atomic="true"
113+
aria-live="polite"
114+
[class.mat-mdc-form-field-error-wrapper]="subscriptMessageType === 'error'"
115+
[class.mat-mdc-form-field-hint-wrapper]="subscriptMessageType === 'hint'">
113116
@switch (subscriptMessageType) {
114117
@case ('error') {
115118
<ng-content select="mat-error, [matError]"></ng-content>

src/material/input/input.spec.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
} from '../form-field';
3030
import {By} from '@angular/platform-browser';
3131
import {MAT_INPUT_VALUE_ACCESSOR, MatInput, MatInputModule} from './index';
32+
import {MatFormFieldNotchedOutline} from '../form-field/directives/notched-outline';
3233

3334
describe('MatMdcInput without forms', () => {
3435
beforeEach(() => {
@@ -625,6 +626,29 @@ describe('MatMdcInput without forms', () => {
625626
expect(input.getAttribute('aria-describedby')).toBe(`initial ${hintId}`);
626627
}));
627628

629+
it('should show outline label correctly based on initial condition to false', fakeAsync(() => {
630+
const fixture = TestBed.createComponent(MatInputOutlineWithConditionalLabel);
631+
fixture.detectChanges();
632+
tick(16);
633+
634+
const notchedOutline: HTMLElement = fixture.debugElement.query(
635+
By.directive(MatFormFieldNotchedOutline),
636+
).nativeElement;
637+
638+
console.log('notchedOutline', notchedOutline.classList);
639+
640+
expect(notchedOutline.classList).toContain('mdc-notched-outline--no-label');
641+
expect(notchedOutline.classList).not.toContain('mdc-notched-outline--upgraded');
642+
643+
fixture.componentInstance.showLabel = true;
644+
fixture.changeDetectorRef.markForCheck();
645+
fixture.detectChanges();
646+
tick(16);
647+
648+
expect(notchedOutline.classList).not.toContain('mdc-notched-outline--no-label');
649+
expect(notchedOutline.classList).toContain('mdc-notched-outline--upgraded');
650+
}));
651+
628652
it('supports user binding to aria-describedby', fakeAsync(() => {
629653
const fixture = TestBed.createComponent(MatInputWithSubscriptAndAriaDescribedBy);
630654

@@ -2170,6 +2194,22 @@ class MatInputWithAppearance {
21702194
appearance: MatFormFieldAppearance;
21712195
}
21722196

2197+
@Component({
2198+
template: `
2199+
<mat-form-field appearance="outline">
2200+
@if(showLabel) {
2201+
<mat-label>My Label</mat-label>
2202+
}
2203+
<input matInput placeholder="Placeholder">
2204+
</mat-form-field>
2205+
`,
2206+
imports: [MatInputModule],
2207+
})
2208+
class MatInputOutlineWithConditionalLabel {
2209+
@ViewChild(MatFormField) formField: MatFormField;
2210+
showLabel: boolean = false;
2211+
}
2212+
21732213
@Component({
21742214
template: `
21752215
<mat-form-field [subscriptSizing]="sizing">

0 commit comments

Comments
 (0)