99import { AbstractControl , FormGroupDirective , NgControl , NgForm } from '@angular/forms' ;
1010import { Subject } from 'rxjs' ;
1111import { ErrorStateMatcher as _ErrorStateMatcher } from '../error/error-options' ;
12+ import { FormField } from '@angular/forms/signals' ;
13+ import { isSignal } from '@angular/core' ;
1214
1315// Declare ErrorStateMatcher as an interface to have compatibility with Closure Compiler.
1416interface ErrorStateMatcher extends _ErrorStateMatcher { }
@@ -24,21 +26,58 @@ export class _ErrorStateTracker {
2426 /** User-defined matcher for the error state. */
2527 matcher ! : ErrorStateMatcher ;
2628
29+ /** Reactive or template-based control directive. */
30+ ngControl : NgControl | null ;
31+
32+ /** Signal-based form field directive. */
33+ formField : FormField < unknown > | null ;
34+
2735 constructor (
2836 private _defaultMatcher : ErrorStateMatcher | null ,
29- public ngControl : NgControl | null ,
37+ directive : NgControl | FormField < unknown > | null ,
3038 private _parentFormGroup : FormGroupDirective | null ,
3139 private _parentForm : NgForm | null ,
3240 private _stateChanges : Subject < void > ,
33- ) { }
41+ ) {
42+ if ( ! directive ) {
43+ this . ngControl = this . formField = null ;
44+ } else if (
45+ isSignal ( ( directive as { field ?: unknown } ) . field ) &&
46+ // Avoid false positives for interop controls.
47+ ! ( directive as { updateValueAndValidity ?: unknown } ) . updateValueAndValidity
48+ ) {
49+ this . formField = directive as FormField < unknown > ;
50+ this . ngControl = null ;
51+ } else {
52+ this . formField = null ;
53+ this . ngControl = directive as NgControl ;
54+ }
55+ }
3456
3557 /** Updates the error state based on the provided error state matcher. */
3658 updateErrorState ( ) {
3759 const oldState = this . errorState ;
38- const parent = this . _parentFormGroup || this . _parentForm ;
3960 const matcher = this . matcher || this . _defaultMatcher ;
40- const control = this . ngControl ? ( this . ngControl . control as AbstractControl ) : null ;
41- const newState = matcher ?. isErrorState ( control , parent ) ?? false ;
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+ }
4281
4382 if ( newState !== oldState ) {
4483 this . errorState = newState ;
0 commit comments