From 4b588d181b18beb74df0560318b129aea1036a63 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Fri, 13 Aug 2021 06:49:35 +0200 Subject: [PATCH] fix(material/input): show required asterisk when using required validator 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. --- .../mdc-input/input.spec.ts | 31 +++++++++++++++++-- src/material/input/input.spec.ts | 29 +++++++++++++++-- src/material/input/input.ts | 8 +++-- tools/public_api_guard/material/input.md | 2 +- 4 files changed, 60 insertions(+), 10 deletions(-) diff --git a/src/material-experimental/mdc-input/input.spec.ts b/src/material-experimental/mdc-input/input.spec.ts index b971b87f59cd..aeb294f15cb6 100644 --- a/src/material-experimental/mdc-input/input.spec.ts +++ b/src/material-experimental/mdc-input/input.spec.ts @@ -327,6 +327,16 @@ describe('MatMdcInput without forms', () => { expect(label.nativeElement.classList).toContain('mdc-floating-label--required'); })); + it('should show the required star when using a FormControl', fakeAsync(() => { + const fixture = createComponent(MatInputWithRequiredFormControl); + fixture.detectChanges(); + + const label = fixture.debugElement.query(By.css('label'))!; + expect(label).not.toBeNull(); + expect(label.nativeElement.textContent).toBe('Hello'); + expect(label.nativeElement.classList).toContain('mdc-floating-label--required'); + })); + it('should not hide the required star if input is disabled', () => { const fixture = createComponent(MatInputLabelRequiredTestComponent); @@ -1058,7 +1068,7 @@ describe('MatMdcInput with forms', () => { expect(testComponent.formControl.invalid) .withContext('Expected form control to be invalid').toBe(true); - expect(inputEl.value).toBeFalsy(); + expect(inputEl.value).toBe('incorrect'); expect(inputEl.getAttribute('aria-invalid')) .withContext('Expected aria-invalid to be set to "true"').toBe('true'); @@ -1548,7 +1558,10 @@ class MatInputMissingMatInputTestController {} }) class MatInputWithFormErrorMessages { @ViewChild('form') form: NgForm; - formControl = new FormControl('', [Validators.required, Validators.pattern(/valid value/)]); + formControl = new FormControl('incorrect', [ + Validators.required, + Validators.pattern(/valid value/) + ]); renderError = true; } @@ -1591,7 +1604,7 @@ class MatInputWithCustomErrorStateMatcher { class MatInputWithFormGroupErrorMessages { @ViewChild(FormGroupDirective) formGroupDirective: FormGroupDirective; formGroup = new FormGroup({ - name: new FormControl('', [Validators.required, Validators.pattern(/valid value/)]) + name: new FormControl('incorrect', [Validators.required, Validators.pattern(/valid value/)]) }); } @@ -1790,3 +1803,15 @@ class MatInputWithColor { ` }) class MatInputInsideOutsideFormField {} + + +@Component({ + template: ` + + Hello + + ` +}) +class MatInputWithRequiredFormControl { + formControl = new FormControl('', [Validators.required]); +} diff --git a/src/material/input/input.spec.ts b/src/material/input/input.spec.ts index 4532c2fecdf2..5167ffe80445 100644 --- a/src/material/input/input.spec.ts +++ b/src/material/input/input.spec.ts @@ -410,6 +410,15 @@ describe('MatInput without forms', () => { expect(labelEl.nativeElement.textContent).toBe('hello *'); })); + it('should show the required star when using a FormControl', fakeAsync(() => { + const fixture = createComponent(MatInputWithRequiredFormControl); + fixture.detectChanges(); + + const labelEl = fixture.debugElement.query(By.css('label'))!; + expect(labelEl).not.toBeNull(); + expect(labelEl.nativeElement.textContent).toBe('Hello *'); + })); + it('should hide the required star if input is disabled', () => { const fixture = createComponent(MatInputPlaceholderRequiredTestComponent); @@ -1203,7 +1212,7 @@ describe('MatInput with forms', () => { expect(testComponent.formControl.invalid) .withContext('Expected form control to be invalid').toBe(true); - expect(inputEl.value).toBeFalsy(); + expect(inputEl.value).toBe('incorrect'); expect(inputEl.getAttribute('aria-invalid')) .withContext('Expected aria-invalid to be set to "true".').toBe('true'); @@ -2018,7 +2027,10 @@ class MatInputMissingMatInputTestController {} }) class MatInputWithFormErrorMessages { @ViewChild('form') form: NgForm; - formControl = new FormControl('', [Validators.required, Validators.pattern(/valid value/)]); + formControl = new FormControl('incorrect', [ + Validators.required, + Validators.pattern(/valid value/) + ]); renderError = true; } @@ -2061,7 +2073,7 @@ class MatInputWithCustomErrorStateMatcher { class MatInputWithFormGroupErrorMessages { @ViewChild(FormGroupDirective) formGroupDirective: FormGroupDirective; formGroup = new FormGroup({ - name: new FormControl('', [Validators.required, Validators.pattern(/valid value/)]) + name: new FormControl('incorrect', [Validators.required, Validators.pattern(/valid value/)]) }); } @@ -2360,3 +2372,14 @@ class MatInputWithAnotherNgIf { class MatInputWithColor { color: ThemePalette; } + + +@Component({ + template: ` + + + ` +}) +class MatInputWithRequiredFormControl { + formControl = new FormControl('', [Validators.required]); +} diff --git a/src/material/input/input.ts b/src/material/input/input.ts index 527fa03d3902..0391ad8dec1d 100644 --- a/src/material/input/input.ts +++ b/src/material/input/input.ts @@ -23,7 +23,7 @@ import { Optional, Self, } from '@angular/core'; -import {FormGroupDirective, NgControl, NgForm} from '@angular/forms'; +import {FormGroupDirective, NgControl, NgForm, Validators} from '@angular/forms'; import { CanUpdateErrorState, ErrorStateMatcher, @@ -174,9 +174,11 @@ export class MatInput extends _MatInputBase implements MatFormFieldControl, * @docs-private */ @Input() - get required(): boolean { return this._required; } + get required(): boolean { + return this._required ?? this.ngControl?.control?.hasValidator(Validators.required) ?? false; + } set required(value: boolean) { this._required = coerceBooleanProperty(value); } - protected _required = false; + protected _required: boolean | undefined; /** Input type of the element. */ @Input() diff --git a/tools/public_api_guard/material/input.md b/tools/public_api_guard/material/input.md index 5f9e485d2be7..32f3ebc27335 100644 --- a/tools/public_api_guard/material/input.md +++ b/tools/public_api_guard/material/input.md @@ -96,7 +96,7 @@ export class MatInput extends _MatInputBase implements MatFormFieldControl, get required(): boolean; set required(value: boolean); // (undocumented) - protected _required: boolean; + protected _required: boolean | undefined; setDescribedByIds(ids: string[]): void; get shouldLabelFloat(): boolean; readonly stateChanges: Subject;