diff --git a/goldens/public-api/forms/signals/index.api.md b/goldens/public-api/forms/signals/index.api.md index 6d7289d404f..dc19307b37b 100644 --- a/goldens/public-api/forms/signals/index.api.md +++ b/goldens/public-api/forms/signals/index.api.md @@ -520,7 +520,7 @@ export type SchemaPathTree = // @public export interface SignalFormsConfig { classes?: { - [className: string]: (state: FieldState) => boolean; + [className: string]: (state: Field) => boolean; }; } diff --git a/packages/forms/signals/compat/src/api/di.ts b/packages/forms/signals/compat/src/api/di.ts index 635eae2c532..07e1d211328 100644 --- a/packages/forms/signals/compat/src/api/di.ts +++ b/packages/forms/signals/compat/src/api/di.ts @@ -15,11 +15,11 @@ import type {SignalFormsConfig} from '../../../src/api/di'; * @experimental 21.0.1 */ export const NG_STATUS_CLASSES: SignalFormsConfig['classes'] = { - 'ng-touched': (state) => state.touched(), - 'ng-untouched': (state) => !state.touched(), - 'ng-dirty': (state) => state.dirty(), - 'ng-pristine': (state) => !state.dirty(), - 'ng-valid': (state) => state.valid(), - 'ng-invalid': (state) => state.invalid(), - 'ng-pending': (state) => state.pending(), + 'ng-touched': ({state}) => state().touched(), + 'ng-untouched': ({state}) => !state().touched(), + 'ng-dirty': ({state}) => state().dirty(), + 'ng-pristine': ({state}) => !state().dirty(), + 'ng-valid': ({state}) => state().valid(), + 'ng-invalid': ({state}) => state().invalid(), + 'ng-pending': ({state}) => state().pending(), }; diff --git a/packages/forms/signals/src/api/di.ts b/packages/forms/signals/src/api/di.ts index 44b024ca652..86e100fae68 100644 --- a/packages/forms/signals/src/api/di.ts +++ b/packages/forms/signals/src/api/di.ts @@ -8,7 +8,7 @@ import {type Provider} from '@angular/core'; import {SIGNAL_FORMS_CONFIG} from '../field/di'; -import type {FieldState} from './types'; +import type {Field} from './field_directive'; /** * Configuration options for signal forms. @@ -17,7 +17,7 @@ import type {FieldState} from './types'; */ export interface SignalFormsConfig { /** A map of CSS class names to predicate functions that determine when to apply them. */ - classes?: {[className: string]: (state: FieldState) => boolean}; + classes?: {[className: string]: (state: Field) => boolean}; } /** diff --git a/packages/forms/signals/src/api/field_directive.ts b/packages/forms/signals/src/api/field_directive.ts index d4eb9ff5353..cb605eb76ac 100644 --- a/packages/forms/signals/src/api/field_directive.ts +++ b/packages/forms/signals/src/api/field_directive.ts @@ -85,7 +85,8 @@ export class Field { private config = inject(SIGNAL_FORMS_CONFIG, {optional: true}); /** @internal */ readonly classes = Object.entries(this.config?.classes ?? {}).map( - ([className, computation]) => [className, computed(() => computation(this.state()))] as const, + ([className, computation]) => + [className, computed(() => computation(this as Field))] as const, ); /** Any `ControlValueAccessor` instances provided on the host element. */ diff --git a/packages/forms/signals/test/web/field_directive.spec.ts b/packages/forms/signals/test/web/field_directive.spec.ts index e57b1e04bd4..5309fcd4122 100644 --- a/packages/forms/signals/test/web/field_directive.spec.ts +++ b/packages/forms/signals/test/web/field_directive.spec.ts @@ -2653,7 +2653,7 @@ describe('field directive', () => { providers: [ provideSignalFormsConfig({ classes: { - 'my-invalid-class': (state) => state.invalid(), + 'my-invalid-class': ({state}) => state().invalid(), }, }), ], @@ -2770,6 +2770,35 @@ describe('field directive', () => { expect(customCtrl.classList.contains('always')).toBe(true); expect(customSubform.classList.contains('always')).toBe(false); }); + + it('should apply classes based on element', () => { + TestBed.configureTestingModule({ + providers: [ + provideSignalFormsConfig({ + classes: { + 'multiline': ({element}) => element.tagName.toLowerCase() === 'textarea', + }, + }), + ], + }); + + @Component({ + imports: [Field], + template: ` + + + `, + }) + class TestCmp { + readonly f = form(signal('')); + } + + const fixture = act(() => TestBed.createComponent(TestCmp)); + const input = fixture.nativeElement.querySelector('input'); + const textarea = fixture.nativeElement.querySelector('textarea'); + expect(input.classList.contains('multiline')).toBe(false); + expect(textarea.classList.contains('multiline')).toBe(true); + }); }); it('should create & bind input when a macro task is running', async () => {