Skip to content

Commit 69029b4

Browse files
authored
fix(material/input): show required asterisk when using required validator (#23362)
Resolves the long-standing issue where the required asterisk wasn't being shown in the form field label when using the `required` validator from Forms. Fixes #2574.
1 parent 69a7e98 commit 69029b4

File tree

4 files changed

+60
-10
lines changed

4 files changed

+60
-10
lines changed

src/material-experimental/mdc-input/input.spec.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,16 @@ describe('MatMdcInput without forms', () => {
327327
expect(label.nativeElement.classList).toContain('mdc-floating-label--required');
328328
}));
329329

330+
it('should show the required star when using a FormControl', fakeAsync(() => {
331+
const fixture = createComponent(MatInputWithRequiredFormControl);
332+
fixture.detectChanges();
333+
334+
const label = fixture.debugElement.query(By.css('label'))!;
335+
expect(label).not.toBeNull();
336+
expect(label.nativeElement.textContent).toBe('Hello');
337+
expect(label.nativeElement.classList).toContain('mdc-floating-label--required');
338+
}));
339+
330340
it('should not hide the required star if input is disabled', () => {
331341
const fixture = createComponent(MatInputLabelRequiredTestComponent);
332342

@@ -1058,7 +1068,7 @@ describe('MatMdcInput with forms', () => {
10581068

10591069
expect(testComponent.formControl.invalid)
10601070
.withContext('Expected form control to be invalid').toBe(true);
1061-
expect(inputEl.value).toBeFalsy();
1071+
expect(inputEl.value).toBe('incorrect');
10621072
expect(inputEl.getAttribute('aria-invalid'))
10631073
.withContext('Expected aria-invalid to be set to "true"').toBe('true');
10641074

@@ -1548,7 +1558,10 @@ class MatInputMissingMatInputTestController {}
15481558
})
15491559
class MatInputWithFormErrorMessages {
15501560
@ViewChild('form') form: NgForm;
1551-
formControl = new FormControl('', [Validators.required, Validators.pattern(/valid value/)]);
1561+
formControl = new FormControl('incorrect', [
1562+
Validators.required,
1563+
Validators.pattern(/valid value/)
1564+
]);
15521565
renderError = true;
15531566
}
15541567

@@ -1591,7 +1604,7 @@ class MatInputWithCustomErrorStateMatcher {
15911604
class MatInputWithFormGroupErrorMessages {
15921605
@ViewChild(FormGroupDirective) formGroupDirective: FormGroupDirective;
15931606
formGroup = new FormGroup({
1594-
name: new FormControl('', [Validators.required, Validators.pattern(/valid value/)])
1607+
name: new FormControl('incorrect', [Validators.required, Validators.pattern(/valid value/)])
15951608
});
15961609
}
15971610

@@ -1790,3 +1803,15 @@ class MatInputWithColor {
17901803
`
17911804
})
17921805
class MatInputInsideOutsideFormField {}
1806+
1807+
1808+
@Component({
1809+
template: `
1810+
<mat-form-field>
1811+
<mat-label>Hello</mat-label>
1812+
<input matInput [formControl]="formControl">
1813+
</mat-form-field>`
1814+
})
1815+
class MatInputWithRequiredFormControl {
1816+
formControl = new FormControl('', [Validators.required]);
1817+
}

src/material/input/input.spec.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,15 @@ describe('MatInput without forms', () => {
407407
expect(labelEl.nativeElement.textContent).toBe('hello *');
408408
}));
409409

410+
it('should show the required star when using a FormControl', fakeAsync(() => {
411+
const fixture = createComponent(MatInputWithRequiredFormControl);
412+
fixture.detectChanges();
413+
414+
const labelEl = fixture.debugElement.query(By.css('label'))!;
415+
expect(labelEl).not.toBeNull();
416+
expect(labelEl.nativeElement.textContent).toBe('Hello *');
417+
}));
418+
410419
it('should hide the required star if input is disabled', () => {
411420
const fixture = createComponent(MatInputPlaceholderRequiredTestComponent);
412421

@@ -1200,7 +1209,7 @@ describe('MatInput with forms', () => {
12001209

12011210
expect(testComponent.formControl.invalid)
12021211
.withContext('Expected form control to be invalid').toBe(true);
1203-
expect(inputEl.value).toBeFalsy();
1212+
expect(inputEl.value).toBe('incorrect');
12041213
expect(inputEl.getAttribute('aria-invalid'))
12051214
.withContext('Expected aria-invalid to be set to "true".').toBe('true');
12061215

@@ -1977,7 +1986,10 @@ class MatInputMissingMatInputTestController {}
19771986
})
19781987
class MatInputWithFormErrorMessages {
19791988
@ViewChild('form') form: NgForm;
1980-
formControl = new FormControl('', [Validators.required, Validators.pattern(/valid value/)]);
1989+
formControl = new FormControl('incorrect', [
1990+
Validators.required,
1991+
Validators.pattern(/valid value/)
1992+
]);
19811993
renderError = true;
19821994
}
19831995

@@ -2020,7 +2032,7 @@ class MatInputWithCustomErrorStateMatcher {
20202032
class MatInputWithFormGroupErrorMessages {
20212033
@ViewChild(FormGroupDirective) formGroupDirective: FormGroupDirective;
20222034
formGroup = new FormGroup({
2023-
name: new FormControl('', [Validators.required, Validators.pattern(/valid value/)])
2035+
name: new FormControl('incorrect', [Validators.required, Validators.pattern(/valid value/)])
20242036
});
20252037
}
20262038

@@ -2268,3 +2280,14 @@ class MatInputWithAnotherNgIf {
22682280
class MatInputWithColor {
22692281
color: ThemePalette;
22702282
}
2283+
2284+
2285+
@Component({
2286+
template: `
2287+
<mat-form-field>
2288+
<input matInput placeholder="Hello" [formControl]="formControl">
2289+
</mat-form-field>`
2290+
})
2291+
class MatInputWithRequiredFormControl {
2292+
formControl = new FormControl('', [Validators.required]);
2293+
}

src/material/input/input.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
Optional,
2424
Self,
2525
} from '@angular/core';
26-
import {FormGroupDirective, NgControl, NgForm} from '@angular/forms';
26+
import {FormGroupDirective, NgControl, NgForm, Validators} from '@angular/forms';
2727
import {
2828
CanUpdateErrorState,
2929
ErrorStateMatcher,
@@ -174,9 +174,11 @@ export class MatInput extends _MatInputBase implements MatFormFieldControl<any>,
174174
* @docs-private
175175
*/
176176
@Input()
177-
get required(): boolean { return this._required; }
177+
get required(): boolean {
178+
return this._required ?? this.ngControl?.control?.hasValidator(Validators.required) ?? false;
179+
}
178180
set required(value: boolean) { this._required = coerceBooleanProperty(value); }
179-
protected _required = false;
181+
protected _required: boolean | undefined;
180182

181183
/** Input type of the element. */
182184
@Input()

tools/public_api_guard/material/input.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export class MatInput extends _MatInputBase implements MatFormFieldControl<any>,
9595
get required(): boolean;
9696
set required(value: boolean);
9797
// (undocumented)
98-
protected _required: boolean;
98+
protected _required: boolean | undefined;
9999
setDescribedByIds(ids: string[]): void;
100100
get shouldLabelFloat(): boolean;
101101
readonly stateChanges: Subject<void>;

0 commit comments

Comments
 (0)