diff --git a/src/material/chips/chip-grid.ts b/src/material/chips/chip-grid.ts index 4c4befd4d818..c77343c3ef69 100644 --- a/src/material/chips/chip-grid.ts +++ b/src/material/chips/chip-grid.ts @@ -30,6 +30,7 @@ import { NgForm, Validators, } from '@angular/forms'; +import {FORM_FIELD} from '@angular/forms/signals'; import {_ErrorStateTracker, ErrorStateMatcher} from '../core'; import {MatFormFieldControl} from '../form-field'; import {merge, Observable, Subject} from 'rxjs'; @@ -258,6 +259,7 @@ export class MatChipGrid const parentForm = inject(NgForm, {optional: true}); const parentFormGroup = inject(FormGroupDirective, {optional: true}); const defaultErrorStateMatcher = inject(ErrorStateMatcher); + const formField = inject(FORM_FIELD, {optional: true, self: true}); if (this.ngControl) { this.ngControl.valueAccessor = this; @@ -265,7 +267,7 @@ export class MatChipGrid this._errorStateTracker = new _ErrorStateTracker( defaultErrorStateMatcher, - this.ngControl, + formField || this.ngControl, parentFormGroup, parentForm, this.stateChanges, @@ -492,7 +494,7 @@ export class MatChipGrid this.stateChanges.next(); } - protected override _redirectDestroyedChipFocus() { + protected override _redirectDestroyedChipFocus() { if (this._lastDestroyedFocusedChipIndex === null) { return; } @@ -501,8 +503,7 @@ export class MatChipGrid // If there are no chips left, or the set focuses the input, // clear the active item silently to prevent stale references. - if (!this._chips.length || - (this._chips.length === 1 && this._chips.first.disabled)) { + if (!this._chips.length || (this._chips.length === 1 && this._chips.first.disabled)) { this._keyManager.updateActiveItem(-1); } } diff --git a/src/material/core/common-behaviors/error-state.ts b/src/material/core/common-behaviors/error-state.ts index 33cfa9113c80..b8dd602d78df 100644 --- a/src/material/core/common-behaviors/error-state.ts +++ b/src/material/core/common-behaviors/error-state.ts @@ -57,31 +57,21 @@ export class _ErrorStateTracker { /** Updates the error state based on the provided error state matcher. */ updateErrorState() { const oldState = this.errorState; - const matcher = this.matcher || this._defaultMatcher; - let newState: boolean; - - if (this.formField) { - if ( - (typeof ngDevMode === 'undefined' || ngDevMode) && - matcher && - !matcher.isSignalErrorState - ) { - throw new Error( - 'Current error state matcher does not support signal forms. ' + - 'Please implement the `isSignalErrorState` method.', - ); - } - - newState = matcher?.isSignalErrorState?.(this.formField.field()) ?? false; - } else { - const parent = this._parentFormGroup || this._parentForm; - const control = this.ngControl ? (this.ngControl.control as AbstractControl) : null; - newState = matcher?.isErrorState(control, parent) ?? false; - } + const newState = this._getCurrentErrorState(this.matcher || this._defaultMatcher); if (newState !== oldState) { this.errorState = newState; this._stateChanges.next(); } } + + private _getCurrentErrorState(matcher: ErrorStateMatcher | undefined): boolean { + if (this.formField && matcher?.isSignalErrorState) { + return matcher.isSignalErrorState(this.formField.field()) ?? false; + } + + const parent = this._parentFormGroup || this._parentForm; + const control = this.ngControl ? (this.ngControl.control as AbstractControl) : null; + return matcher?.isErrorState(control, parent) ?? false; + } } diff --git a/src/material/datepicker/date-range-input-parts.ts b/src/material/datepicker/date-range-input-parts.ts index a8aa585bb2f4..62c2b810088c 100644 --- a/src/material/datepicker/date-range-input-parts.ts +++ b/src/material/datepicker/date-range-input-parts.ts @@ -30,6 +30,7 @@ import { ValidatorFn, Validators, } from '@angular/forms'; +import {FORM_FIELD} from '@angular/forms/signals'; import {ErrorStateMatcher, _ErrorStateTracker} from '../core'; import {_computeAriaAccessibleName} from './aria-accessible-name'; import {DateRange, DateSelectionModelChange} from './date-selection-model'; @@ -103,6 +104,11 @@ abstract class MatDateRangeInputPartBase // everything has been resolved. const ngControl = this._injector.get(NgControl, null, {optional: true, self: true}); + this._errorStateTracker.formField = this._injector.get(FORM_FIELD, null, { + optional: true, + self: true, + }); + if (ngControl) { this.ngControl = ngControl; this._errorStateTracker.ngControl = ngControl; diff --git a/src/material/input/input.ts b/src/material/input/input.ts index d0ba16102861..ab71331eaaf8 100644 --- a/src/material/input/input.ts +++ b/src/material/input/input.ts @@ -28,6 +28,7 @@ import { } from '@angular/core'; import {_IdGenerator} from '@angular/cdk/a11y'; import {FormGroupDirective, NgControl, NgForm, Validators} from '@angular/forms'; +import {FORM_FIELD} from '@angular/forms/signals'; import {ErrorStateMatcher, _ErrorStateTracker} from '../core'; import {MatFormFieldControl, MatFormField, MAT_FORM_FIELD} from '../form-field'; import {Subject} from 'rxjs'; @@ -299,6 +300,7 @@ export class MatInput const parentFormGroup = inject(FormGroupDirective, {optional: true}); const defaultErrorStateMatcher = inject(ErrorStateMatcher); const accessor = inject(MAT_INPUT_VALUE_ACCESSOR, {optional: true, self: true}); + const formField = inject(FORM_FIELD, {optional: true, self: true}); const element = this._elementRef.nativeElement; const nodeName = element.nodeName.toLowerCase(); @@ -331,7 +333,7 @@ export class MatInput this._errorStateTracker = new _ErrorStateTracker( defaultErrorStateMatcher, - this.ngControl, + formField || this.ngControl, parentFormGroup, parentForm, this.stateChanges, diff --git a/src/material/select/select.ts b/src/material/select/select.ts index 1da854febeac..59415b689663 100644 --- a/src/material/select/select.ts +++ b/src/material/select/select.ts @@ -67,6 +67,7 @@ import { Validators, } from '@angular/forms'; import {_getEventTarget} from '@angular/cdk/platform'; +import {FORM_FIELD} from '@angular/forms/signals'; import { _animationsDisabled, _countGroupLabelsBeforeOption, @@ -595,6 +596,7 @@ export class MatSelect const parentFormGroup = inject(FormGroupDirective, {optional: true}); const tabIndex = inject(new HostAttributeToken('tabindex'), {optional: true}); const defaultPopoverConfig = inject(OVERLAY_DEFAULT_CONFIG, {optional: true}); + const formField = inject(FORM_FIELD, {optional: true, self: true}); if (this.ngControl) { // Note: we provide the value accessor through here, instead of @@ -610,7 +612,7 @@ export class MatSelect this._errorStateTracker = new _ErrorStateTracker( defaultErrorStateMatcher, - this.ngControl, + formField || this.ngControl, parentFormGroup, parentForm, this.stateChanges,