@@ -22,6 +22,7 @@ import {
22
22
OutputRef ,
23
23
OutputRefSubscription ,
24
24
reflectComponentType ,
25
+ Renderer2 ,
25
26
signal ,
26
27
Type ,
27
28
untracked ,
@@ -37,7 +38,7 @@ import {
37
38
PATTERN ,
38
39
REQUIRED ,
39
40
} from '../api/property' ;
40
- import { Field } from '../api/types' ;
41
+ import type { Field } from '../api/types' ;
41
42
import type { FieldNode } from '../field/node' ;
42
43
import {
43
44
illegallyGetComponentInstance ,
@@ -75,6 +76,7 @@ import {InteropNgControl} from './interop_ng_control';
75
76
export class Control < T > {
76
77
/** The injector for this component. */
77
78
private readonly injector = inject ( Injector ) ;
79
+ private readonly renderer = inject ( Renderer2 ) ;
78
80
79
81
/** Whether state synchronization with the field has been setup yet. */
80
82
private initialized = false ;
@@ -202,16 +204,25 @@ export class Control<T> {
202
204
} ) ;
203
205
input . addEventListener ( 'blur' , ( ) => this . state ( ) . markAsTouched ( ) ) ;
204
206
205
- this . maybeSynchronize ( ( ) => this . state ( ) . readonly ( ) , withBooleanAttribute ( input , 'readonly' ) ) ;
207
+ this . maybeSynchronize (
208
+ ( ) => this . state ( ) . readonly ( ) ,
209
+ this . withBooleanAttribute ( input , 'readonly' ) ,
210
+ ) ;
206
211
// TODO: consider making a global configuration option for using aria-disabled instead.
207
- this . maybeSynchronize ( ( ) => this . state ( ) . disabled ( ) , withBooleanAttribute ( input , 'disabled' ) ) ;
208
- this . maybeSynchronize ( ( ) => this . state ( ) . name ( ) , withAttribute ( input , 'name' ) ) ;
212
+ this . maybeSynchronize (
213
+ ( ) => this . state ( ) . disabled ( ) ,
214
+ this . withBooleanAttribute ( input , 'disabled' ) ,
215
+ ) ;
216
+ this . maybeSynchronize ( ( ) => this . state ( ) . name ( ) , this . withAttribute ( input , 'name' ) ) ;
209
217
210
- this . maybeSynchronize ( this . propertySource ( REQUIRED ) , withBooleanAttribute ( input , 'required' ) ) ;
211
- this . maybeSynchronize ( this . propertySource ( MIN ) , withAttribute ( input , 'min' ) ) ;
212
- this . maybeSynchronize ( this . propertySource ( MIN_LENGTH ) , withAttribute ( input , 'minLength' ) ) ;
213
- this . maybeSynchronize ( this . propertySource ( MAX ) , withAttribute ( input , 'max' ) ) ;
214
- this . maybeSynchronize ( this . propertySource ( MAX_LENGTH ) , withAttribute ( input , 'maxLength' ) ) ;
218
+ this . maybeSynchronize (
219
+ this . propertySource ( REQUIRED ) ,
220
+ this . withBooleanAttribute ( input , 'required' ) ,
221
+ ) ;
222
+ this . maybeSynchronize ( this . propertySource ( MIN ) , this . withAttribute ( input , 'min' ) ) ;
223
+ this . maybeSynchronize ( this . propertySource ( MIN_LENGTH ) , this . withAttribute ( input , 'minLength' ) ) ;
224
+ this . maybeSynchronize ( this . propertySource ( MAX ) , this . withAttribute ( input , 'max' ) ) ;
225
+ this . maybeSynchronize ( this . propertySource ( MAX_LENGTH ) , this . withAttribute ( input , 'maxLength' ) ) ;
215
226
216
227
switch ( inputType ) {
217
228
case 'checkbox' :
@@ -360,38 +371,38 @@ export class Control<T> {
360
371
) ;
361
372
return ( ) => metaSource ( ) ?.( ) ;
362
373
}
374
+
375
+ /** Creates a (non-boolean) value sync that writes the given attribute of the given element. */
376
+ private withAttribute (
377
+ element : HTMLElement ,
378
+ attribute : string ,
379
+ ) : ( value : { toString ( ) : string } | undefined ) => void {
380
+ return ( value ) => {
381
+ if ( value !== undefined ) {
382
+ this . renderer . setAttribute ( element , attribute , value . toString ( ) ) ;
383
+ } else {
384
+ this . renderer . removeAttribute ( element , attribute ) ;
385
+ }
386
+ } ;
387
+ }
388
+
389
+ /** Creates a boolean value sync that writes the given attribute of the given element. */
390
+ private withBooleanAttribute ( element : HTMLElement , attribute : string ) : ( value : boolean ) => void {
391
+ return ( value ) => {
392
+ if ( value ) {
393
+ this . renderer . setAttribute ( element , attribute , '' ) ;
394
+ } else {
395
+ this . renderer . removeAttribute ( element , attribute ) ;
396
+ }
397
+ } ;
398
+ }
363
399
}
364
400
365
401
/** Creates a value sync from an input signal. */
366
402
function withInput < T > ( input : InputSignal < T > | undefined ) : ( ( value : T ) => void ) | undefined {
367
403
return input ? ( value : T ) => illegallySetInputSignal ( input , value ) : undefined ;
368
404
}
369
405
370
- /** Creates a boolean value sync that writes the given attribute of the given element. */
371
- function withBooleanAttribute ( element : HTMLElement , attribute : string ) : ( value : boolean ) => void {
372
- return ( value ) => {
373
- if ( value ) {
374
- element . setAttribute ( attribute , '' ) ;
375
- } else {
376
- element . removeAttribute ( attribute ) ;
377
- }
378
- } ;
379
- }
380
-
381
- /** Creates a (non-boolean) value sync that writes the given attribute of the given element. */
382
- function withAttribute (
383
- element : HTMLElement ,
384
- attribute : string ,
385
- ) : ( value : { toString ( ) : string } | undefined ) => void {
386
- return ( value ) => {
387
- if ( value !== undefined ) {
388
- element . setAttribute ( attribute , value . toString ( ) ) ;
389
- } else {
390
- element . removeAttribute ( attribute ) ;
391
- }
392
- } ;
393
- }
394
-
395
406
/**
396
407
* Checks whether the given component matches the contract for either FormValueControl or
397
408
* FormCheckboxControl.
0 commit comments