Skip to content

Commit cdb1592

Browse files
authored
fix(material/form-field): trigger CD when form (#30395)
gets reassigned fixes the issue we were not marking component for changes when form is reassigned making it not update UI for required asterisk fixes #29066
1 parent d7150a4 commit cdb1592

File tree

2 files changed

+90
-1
lines changed

2 files changed

+90
-1
lines changed

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

+22-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import {
3333
contentChild,
3434
inject,
3535
} from '@angular/core';
36-
import {AbstractControlDirective} from '@angular/forms';
36+
import {AbstractControlDirective, ValidatorFn} from '@angular/forms';
3737
import {ThemePalette} from '@angular/material/core';
3838
import {_IdGenerator} from '@angular/cdk/a11y';
3939
import {Subject, Subscription, merge} from 'rxjs';
@@ -322,6 +322,7 @@ export class MatFormField
322322
private _explicitFormFieldControl: MatFormFieldControl<any>;
323323
private _needsOutlineLabelOffsetUpdate = false;
324324
private _previousControl: MatFormFieldControl<unknown> | null = null;
325+
private _previousControlValidatorFn: ValidatorFn | null = null;
325326
private _stateChanges: Subscription | undefined;
326327
private _valueChanges: Subscription | undefined;
327328
private _describedByChanges: Subscription | undefined;
@@ -374,10 +375,30 @@ export class MatFormField
374375
ngAfterContentChecked() {
375376
this._assertFormFieldControl();
376377

378+
// if form field was being used with an input in first place and then replaced by other
379+
// component such as select.
377380
if (this._control !== this._previousControl) {
378381
this._initializeControl(this._previousControl);
382+
383+
// keep a reference for last validator we had.
384+
if (this._control.ngControl && this._control.ngControl.control) {
385+
this._previousControlValidatorFn = this._control.ngControl.control.validator;
386+
}
387+
379388
this._previousControl = this._control;
380389
}
390+
391+
// make sure the the control has been initialized.
392+
if (this._control.ngControl && this._control.ngControl.control) {
393+
// get the validators for current control.
394+
const validatorFn = this._control.ngControl.control.validator;
395+
396+
// if our current validatorFn isn't equal to it might be we are CD behind, marking the
397+
// component will allow us to catch up.
398+
if (validatorFn !== this._previousControlValidatorFn) {
399+
this._changeDetectorRef.markForCheck();
400+
}
401+
}
381402
}
382403

383404
ngOnDestroy() {

src/material/input/input.spec.ts

+68
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,48 @@ describe('MatMdcInput without forms', () => {
348348
expect(label.nativeElement.querySelector('.mat-mdc-form-field-required-marker')).toBeTruthy();
349349
}));
350350

351+
it('should show the required star when FormControl is reassigned', fakeAsync(() => {
352+
const fixture = createComponent(MatInputWithRequiredAssignableFormControl);
353+
fixture.detectChanges();
354+
355+
// should have star by default
356+
let label = fixture.debugElement.query(By.css('label'))!;
357+
expect(label.nativeElement.querySelector('.mat-mdc-form-field-required-marker')).toBeTruthy();
358+
359+
fixture.componentInstance.reassignFormControl();
360+
fixture.changeDetectorRef.markForCheck();
361+
fixture.detectChanges();
362+
363+
// should be removed as form was reassigned with no required validators
364+
label = fixture.debugElement.query(By.css('label'))!;
365+
expect(label.nativeElement.querySelector('.mat-mdc-form-field-required-marker')).toBeFalsy();
366+
}));
367+
368+
it('should show the required star when required validator is toggled', fakeAsync(() => {
369+
const fixture = createComponent(MatInputWithRequiredAssignableFormControl);
370+
fixture.detectChanges();
371+
372+
// should have star by default
373+
let label = fixture.debugElement.query(By.css('label'))!;
374+
expect(label.nativeElement.querySelector('.mat-mdc-form-field-required-marker')).toBeTruthy();
375+
376+
fixture.componentInstance.removeRequiredValidtor();
377+
fixture.changeDetectorRef.markForCheck();
378+
fixture.detectChanges();
379+
380+
// should be removed as control validator was removed
381+
label = fixture.debugElement.query(By.css('label'))!;
382+
expect(label.nativeElement.querySelector('.mat-mdc-form-field-required-marker')).toBeFalsy();
383+
384+
fixture.componentInstance.addRequiredValidator();
385+
fixture.changeDetectorRef.markForCheck();
386+
fixture.detectChanges();
387+
388+
// should contain star as control validator was readded
389+
label = fixture.debugElement.query(By.css('label'))!;
390+
expect(label.nativeElement.querySelector('.mat-mdc-form-field-required-marker')).toBeTruthy();
391+
}));
392+
351393
it('should not hide the required star if input is disabled', () => {
352394
const fixture = createComponent(MatInputLabelRequiredTestComponent);
353395

@@ -2321,3 +2363,29 @@ class MatInputSimple {}
23212363
standalone: false,
23222364
})
23232365
class InputWithNgContainerPrefixAndSuffix {}
2366+
2367+
@Component({
2368+
template: `
2369+
<mat-form-field>
2370+
<mat-label>Hello</mat-label>
2371+
<input matInput [formControl]="formControl">
2372+
</mat-form-field>`,
2373+
standalone: false,
2374+
})
2375+
class MatInputWithRequiredAssignableFormControl {
2376+
formControl = new FormControl('', [Validators.required]);
2377+
2378+
reassignFormControl() {
2379+
this.formControl = new FormControl();
2380+
}
2381+
2382+
addRequiredValidator() {
2383+
this.formControl.setValidators([Validators.required]);
2384+
this.formControl.updateValueAndValidity();
2385+
}
2386+
2387+
removeRequiredValidtor() {
2388+
this.formControl.setValidators([]);
2389+
this.formControl.updateValueAndValidity();
2390+
}
2391+
}

0 commit comments

Comments
 (0)