Skip to content

Commit 2994a25

Browse files
committed
fix(material/form-field): trigger CD when form
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 622152d commit 2994a25

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
@@ -349,6 +349,48 @@ describe('MatMdcInput without forms', () => {
349349
expect(label.nativeElement.querySelector('.mat-mdc-form-field-required-marker')).toBeTruthy();
350350
}));
351351

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

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

0 commit comments

Comments
 (0)