Skip to content

Commit ebd64a1

Browse files
authored
fix(multiple): pass form field to error state tracker (#33509)
Updates a few components that depend on the `_ErrorStateTracker` to pass the signal forms `FormField` directive as well.
1 parent dde1165 commit ebd64a1

5 files changed

Lines changed: 28 additions & 27 deletions

File tree

src/material/chips/chip-grid.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
NgForm,
3131
Validators,
3232
} from '@angular/forms';
33+
import {FORM_FIELD} from '@angular/forms/signals';
3334
import {_ErrorStateTracker, ErrorStateMatcher} from '../core';
3435
import {MatFormFieldControl} from '../form-field';
3536
import {merge, Observable, Subject} from 'rxjs';
@@ -258,14 +259,15 @@ export class MatChipGrid
258259
const parentForm = inject(NgForm, {optional: true});
259260
const parentFormGroup = inject(FormGroupDirective, {optional: true});
260261
const defaultErrorStateMatcher = inject(ErrorStateMatcher);
262+
const formField = inject(FORM_FIELD, {optional: true, self: true});
261263

262264
if (this.ngControl) {
263265
this.ngControl.valueAccessor = this;
264266
}
265267

266268
this._errorStateTracker = new _ErrorStateTracker(
267269
defaultErrorStateMatcher,
268-
this.ngControl,
270+
formField || this.ngControl,
269271
parentFormGroup,
270272
parentForm,
271273
this.stateChanges,
@@ -492,7 +494,7 @@ export class MatChipGrid
492494
this.stateChanges.next();
493495
}
494496

495-
protected override _redirectDestroyedChipFocus() {
497+
protected override _redirectDestroyedChipFocus() {
496498
if (this._lastDestroyedFocusedChipIndex === null) {
497499
return;
498500
}
@@ -501,8 +503,7 @@ export class MatChipGrid
501503

502504
// If there are no chips left, or the set focuses the input,
503505
// clear the active item silently to prevent stale references.
504-
if (!this._chips.length ||
505-
(this._chips.length === 1 && this._chips.first.disabled)) {
506+
if (!this._chips.length || (this._chips.length === 1 && this._chips.first.disabled)) {
506507
this._keyManager.updateActiveItem(-1);
507508
}
508509
}

src/material/core/common-behaviors/error-state.ts

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -57,31 +57,21 @@ export class _ErrorStateTracker {
5757
/** Updates the error state based on the provided error state matcher. */
5858
updateErrorState() {
5959
const oldState = this.errorState;
60-
const matcher = this.matcher || this._defaultMatcher;
61-
let newState: boolean;
62-
63-
if (this.formField) {
64-
if (
65-
(typeof ngDevMode === 'undefined' || ngDevMode) &&
66-
matcher &&
67-
!matcher.isSignalErrorState
68-
) {
69-
throw new Error(
70-
'Current error state matcher does not support signal forms. ' +
71-
'Please implement the `isSignalErrorState` method.',
72-
);
73-
}
74-
75-
newState = matcher?.isSignalErrorState?.(this.formField.field()) ?? false;
76-
} else {
77-
const parent = this._parentFormGroup || this._parentForm;
78-
const control = this.ngControl ? (this.ngControl.control as AbstractControl) : null;
79-
newState = matcher?.isErrorState(control, parent) ?? false;
80-
}
60+
const newState = this._getCurrentErrorState(this.matcher || this._defaultMatcher);
8161

8262
if (newState !== oldState) {
8363
this.errorState = newState;
8464
this._stateChanges.next();
8565
}
8666
}
67+
68+
private _getCurrentErrorState(matcher: ErrorStateMatcher | undefined): boolean {
69+
if (this.formField && matcher?.isSignalErrorState) {
70+
return matcher.isSignalErrorState(this.formField.field()) ?? false;
71+
}
72+
73+
const parent = this._parentFormGroup || this._parentForm;
74+
const control = this.ngControl ? (this.ngControl.control as AbstractControl) : null;
75+
return matcher?.isErrorState(control, parent) ?? false;
76+
}
8777
}

src/material/datepicker/date-range-input-parts.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
ValidatorFn,
3131
Validators,
3232
} from '@angular/forms';
33+
import {FORM_FIELD} from '@angular/forms/signals';
3334
import {ErrorStateMatcher, _ErrorStateTracker} from '../core';
3435
import {_computeAriaAccessibleName} from './aria-accessible-name';
3536
import {DateRange, DateSelectionModelChange} from './date-selection-model';
@@ -103,6 +104,11 @@ abstract class MatDateRangeInputPartBase<D>
103104
// everything has been resolved.
104105
const ngControl = this._injector.get(NgControl, null, {optional: true, self: true});
105106

107+
this._errorStateTracker.formField = this._injector.get(FORM_FIELD, null, {
108+
optional: true,
109+
self: true,
110+
});
111+
106112
if (ngControl) {
107113
this.ngControl = ngControl;
108114
this._errorStateTracker.ngControl = ngControl;

src/material/input/input.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
} from '@angular/core';
2929
import {_IdGenerator} from '@angular/cdk/a11y';
3030
import {FormGroupDirective, NgControl, NgForm, Validators} from '@angular/forms';
31+
import {FORM_FIELD} from '@angular/forms/signals';
3132
import {ErrorStateMatcher, _ErrorStateTracker} from '../core';
3233
import {MatFormFieldControl, MatFormField, MAT_FORM_FIELD} from '../form-field';
3334
import {Subject} from 'rxjs';
@@ -299,6 +300,7 @@ export class MatInput
299300
const parentFormGroup = inject(FormGroupDirective, {optional: true});
300301
const defaultErrorStateMatcher = inject(ErrorStateMatcher);
301302
const accessor = inject(MAT_INPUT_VALUE_ACCESSOR, {optional: true, self: true});
303+
const formField = inject(FORM_FIELD, {optional: true, self: true});
302304

303305
const element = this._elementRef.nativeElement;
304306
const nodeName = element.nodeName.toLowerCase();
@@ -331,7 +333,7 @@ export class MatInput
331333

332334
this._errorStateTracker = new _ErrorStateTracker(
333335
defaultErrorStateMatcher,
334-
this.ngControl,
336+
formField || this.ngControl,
335337
parentFormGroup,
336338
parentForm,
337339
this.stateChanges,

src/material/select/select.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import {
6767
Validators,
6868
} from '@angular/forms';
6969
import {_getEventTarget} from '@angular/cdk/platform';
70+
import {FORM_FIELD} from '@angular/forms/signals';
7071
import {
7172
_animationsDisabled,
7273
_countGroupLabelsBeforeOption,
@@ -595,6 +596,7 @@ export class MatSelect
595596
const parentFormGroup = inject(FormGroupDirective, {optional: true});
596597
const tabIndex = inject(new HostAttributeToken('tabindex'), {optional: true});
597598
const defaultPopoverConfig = inject(OVERLAY_DEFAULT_CONFIG, {optional: true});
599+
const formField = inject(FORM_FIELD, {optional: true, self: true});
598600

599601
if (this.ngControl) {
600602
// Note: we provide the value accessor through here, instead of
@@ -610,7 +612,7 @@ export class MatSelect
610612

611613
this._errorStateTracker = new _ErrorStateTracker(
612614
defaultErrorStateMatcher,
613-
this.ngControl,
615+
formField || this.ngControl,
614616
parentFormGroup,
615617
parentForm,
616618
this.stateChanges,

0 commit comments

Comments
 (0)