Skip to content

Commit ec8c300

Browse files
naaajiimmalerba
authored andcommitted
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 (cherry picked from commit cdb1592)
1 parent 4a661cd commit ec8c300

File tree

2 files changed

+90
-1
lines changed

2 files changed

+90
-1
lines changed

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
contentChild,
3333
inject,
3434
} from '@angular/core';
35-
import {AbstractControlDirective} from '@angular/forms';
35+
import {AbstractControlDirective, ValidatorFn} from '@angular/forms';
3636
import {ThemePalette} from '@angular/material/core';
3737
import {_IdGenerator} from '@angular/cdk/a11y';
3838
import {Subject, Subscription, merge} from 'rxjs';
@@ -326,6 +326,7 @@ export class MatFormField
326326
private _explicitFormFieldControl: MatFormFieldControl<any>;
327327
private _needsOutlineLabelOffsetUpdate = false;
328328
private _previousControl: MatFormFieldControl<unknown> | null = null;
329+
private _previousControlValidatorFn: ValidatorFn | null = null;
329330
private _stateChanges: Subscription | undefined;
330331
private _valueChanges: Subscription | undefined;
331332
private _describedByChanges: Subscription | undefined;
@@ -369,10 +370,30 @@ export class MatFormField
369370
ngAfterContentChecked() {
370371
this._assertFormFieldControl();
371372

373+
// if form field was being used with an input in first place and then replaced by other
374+
// component such as select.
372375
if (this._control !== this._previousControl) {
373376
this._initializeControl(this._previousControl);
377+
378+
// keep a reference for last validator we had.
379+
if (this._control.ngControl && this._control.ngControl.control) {
380+
this._previousControlValidatorFn = this._control.ngControl.control.validator;
381+
}
382+
374383
this._previousControl = this._control;
375384
}
385+
386+
// make sure the the control has been initialized.
387+
if (this._control.ngControl && this._control.ngControl.control) {
388+
// get the validators for current control.
389+
const validatorFn = this._control.ngControl.control.validator;
390+
391+
// if our current validatorFn isn't equal to it might be we are CD behind, marking the
392+
// component will allow us to catch up.
393+
if (validatorFn !== this._previousControlValidatorFn) {
394+
this._changeDetectorRef.markForCheck();
395+
}
396+
}
376397
}
377398

378399
ngOnDestroy() {

src/material/input/input.spec.ts

Lines changed: 68 additions & 0 deletions
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

@@ -2333,3 +2375,29 @@ class MatInputSimple {}
23332375
standalone: false,
23342376
})
23352377
class InputWithNgContainerPrefixAndSuffix {}
2378+
2379+
@Component({
2380+
template: `
2381+
<mat-form-field>
2382+
<mat-label>Hello</mat-label>
2383+
<input matInput [formControl]="formControl">
2384+
</mat-form-field>`,
2385+
standalone: false,
2386+
})
2387+
class MatInputWithRequiredAssignableFormControl {
2388+
formControl = new FormControl('', [Validators.required]);
2389+
2390+
reassignFormControl() {
2391+
this.formControl = new FormControl();
2392+
}
2393+
2394+
addRequiredValidator() {
2395+
this.formControl.setValidators([Validators.required]);
2396+
this.formControl.updateValueAndValidity();
2397+
}
2398+
2399+
removeRequiredValidtor() {
2400+
this.formControl.setValidators([]);
2401+
this.formControl.updateValueAndValidity();
2402+
}
2403+
}

0 commit comments

Comments
 (0)