Puede darse el caso de que halles alguna incompatibilidad en tu código o proyecto, recuerda probar siempre en un entorno de testing antes subir el código al trabajo final.
Solución:
TLDR
StackBlitz.
mi-directiva.directiva.ts
/* ... */
ngOnInit ()
const initialOnChange = (this.ngControl.valueAccessor as any).onChange;
(this.ngControl.valueAccessor as any).onChange = (value) => initialOnChange(this.processInput(value));
/* ... */
@HostListener('ngModelChange', ['$event'])
ngModelChange(value: any)
this.ngControl.valueAccessor.writeValue(this.processInput(value));
Respuesta detallada
Veamos por qué no funcionó inicialmente.
Angular tiene descriptores de acceso de valor predeterminado para ciertos elementos, como por ejemplo para input type='text'
, input type='checkbox'
etc…
A ControlValueAccessor
es el intermediario entre la capa VISTA y la capa MODELO. Cuando un usuario escribe en una entrada, VIEW notifica al ControlValueAccessor
que tiene el trabajo de informar al MODELO.
Por ejemplo, cuando el input
ocurre el evento, el onChange
metodo de la ControlValueAccessor
sera llamado. Así es cómo onChange
parece para cadaControlValueAccessor
:
function setUpViewChangePipeline(control: FormControl, dir: NgControl): void
dir.valueAccessor!.registerOnChange((newValue: any) =>
control._pendingValue = newValue;
control._pendingChange = true;
control._pendingDirty = true;
if (control.updateOn === 'change') updateControl(control, dir);
);
La magia sucede en updateControl
:
function updateControl(control: FormControl, dir: NgControl): void
if (control._pendingDirty) control.markAsDirty();
control.setValue(control._pendingValue, emitModelToViewChange: false);
// !
dir.viewToModelUpdate(control._pendingValue);
control._pendingChange = false;
dir.viewToModelUpdate(control._pendingValue);
es lo que invoca el ngModelChange
evento en la directiva personalizada. Lo que esto significa es que el valor del modelo es el valor de la entrada (en minúsculas). Y porqué ControlValueAccessor.writeValue
solamente escribe el valor en la VISTA, habrá un demora entre el valor de VISTA y el valor de MODELO.
Vale la pena mencionar que FormControl.setValue(val)
escribirá val
a ambas cosas capas, VISTA y MODELO, pero si usáramos esto, habría un Bucle infinitoya que setValue()
llamadas internas viewToModelUpdate
(porque hay que actualizar el MODELO), y viewToModelUpdate
llamadas setValue()
.
Veamos una posible solución:
ngOnInit ()
const initialOnChange = (this.ngControl.valueAccessor as any).onChange;
(this.ngControl.valueAccessor as any).onChange = (value) => initialOnChange(this.processInput(value));
Con este enfoque, está modificando sus datos en la capa VISTA, antes de que se envíen a la ControlValueAccessor
.
Y podemos estar seguros de que onChange
existe en cada incorporado ControlValueAccessor
.
Si va a crear uno personalizado, solo asegúrese de que tenga un onChange
propiedad. TypeScript puede ayudarte con eso.
Si desea leer más acerca de las partes internas de @angular/forms
recomendaría echar un vistazo a Una exploración exhaustiva de las formas angulares.
puedes conseguirlo usando
@HostListener('input', ['$event'])
ngModelChange(event: any)
const item = event.target
const value = item.value;
const pos = item.selectionStart;
this.control.control.setValue(this.processInput(value), emit: false );
item.selectionStart = item.selectionEnd = pos
Vea que usamos la entrada @HostListener para obtener el elemento, no solo el valor. Esto nos permite posicionar el cursor en su posición después de cambiar el valor
NOTA: Para hacer una mayúscula simple, es mejor usar css text-transform:uppercase y, cuando queramos obtener el valor, use toUpperCase()
NOTA 2: sobre la máscara, consulte este SO
Reseñas y puntuaciones del post
Si te animas, puedes dejar un post acerca de qué le añadirías a esta crónica.