typescript - ¿Por qué necesito llamar a onChange y onTouch en writeValue al implementar ControlValueAccessor en Angular?
He implementado el siguiente componente. Funciona y se comporta como se espera. No obstante, como la implementación de ControlValueAccessor era nueva para mí, tuve que seguir un blog sin entender los detalles más profundos de algunas secciones. Así que esta es una especie de situación de "funciona pero ¿por qué?!".
@Component({ selector: ..., templateUrl: ..., styleUrls: ...,
providers: [{ provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InputTextComponent),
multi: true }]
})
export class InputComponent implements ControlValueAccessor {
constructor() { }
@Input() info: string;
onChange: any = () => { }
onTouch: any = () => { }
writeValue(input: any): void {
this.info.value = input;
// this.onChange(input);
// this.onTouch();
}
registerOnChange(_: any): void { this.onChange = _; }
registerOnTouched(_: any): void { this.onTouch = _; }
setDisabledState?(state: boolean): void { }
onEdit(value: string): void { this.onChange(value); }
}
Cuando lo hice funcionar, comenté la segunda y tercera línea del método writeValue(...) y, hasta donde puedo decir, nada se rompió. Esas llamadas también son sugeridas de manera consistente por otros blogs, así que estoy convencido de que es incorrecto omitirlas. Sin embargo, no creo en la magia y prefiero tener una razón concreta para hacer las cosas.
¿Por qué es importante ejecutar la llamada a onChange(...) y onTouch(...) en writeValue(...)? ¿Qué saldrá mal y bajo qué circunstancias se puede esperar?
Como misión secundaria, también intenté comentar los otros métodos y descubrí que no podía notar nada extraño cuando eliminé setDisabledState(...). ¿Cuándo se puede esperar que ese cause problemas? ¿Realmente necesita ser implementado (he visto versiones con signo de interrogación tanto antes como después de los paréntesis con parámetros así: setDisabledState?(state: boolean): void { } pero también así: setDisabledState(state: boolean)?: void { })?
Respuesta #1
Lee este artículo que explica el ControlValueAccessor en gran detalle:
- Nunca más te confundas al implementar ControlValueAccessor en formularios de Angular
Normalmente necesitas implementar la interfaz ControlValueAccessor en un componente si se supone que debe ser usado como parte de un formulario Angular.
Comenté la segunda y tercera línea del método writeValue(...) y, por lo que puedo decir, nada se rompió.
Esto probablemente se deba a que no estás aplicando ninguna directiva de formulario - formControl o ngModel que vincule un FormControl a tu componente de entrada personalizado. El FormControl utiliza el método writeValue del InputComponent para la comunicación.
Aquí está la imagen del artículo que mencioné arriba:
El método writeValue es utilizado por formControl para establecer el valor en el control de formulario nativo. El método registerOnChange es utilizado por formControl para registrar una función de devolución de llamada que se espera que se active cada vez que se actualice el control de formulario nativo. El método registerOnTouched se utiliza para indicar que un usuario interactuó con un control.
¿Por qué es importante ejecutar la llamada a onChange(...) y onTouch(...) en writeValue(...)? ¿Qué saldrá mal y bajo qué circunstancias se puede esperar?
Este es el mecanismo por el cual tu control personalizado que implementa ControlValueAccessor notifica al FormControl de Angular que un valor en la entrada ha cambiado o que el usuario interactuó con el control.
...descubrí que no podía decir nada al volverme loco cuando eliminé setDisabledState(...)... ¿Realmente necesita ser implementado?
Como se especifica en la interfaz, esta función es llamada por la API de formularios cuando el estado del control cambia a o desde "DISABLED". Dependiendo del valor, debe habilitar o deshabilitar el elemento DOM correspondiente. Necesitas implementarla si deseas ser notificado cada vez que el estado de un FormControl asociado se vuelva disabled y luego puedes ejecutar alguna lógica personalizada (por ejemplo, deshabilitar tu componente de entrada).
Respuesta #2
No creo que la respuesta aceptada responda a la pregunta más central de manera concisa:
¿Por qué es importante ejecutar la llamada a onChange(...) y onTouch(...) en writeValue(...)?
No lo es. No necesitas llamar a onChange en writeValue. No es el uso previsto (consulta el enlace de la documentación a continuación).
¿Qué saldrá mal y bajo qué circunstancias se puede esperar?
No esperes que algo salga mal. Respuesta elaborada:
Si llamases a onChange desde dentro de writeValue, notarás:
- La primera llamada a onChange no hace nada. Eso se debe a que la función de retorno de onChange no se ha registrado (es decir, no se ha llamado a
registerOnChange) cuando se llama por primera vez a writeValue. - Llamadas posteriores a onChange desde dentro de writeValue actualizan tu modelo (con
emitModelToViewChange=falsepara evitar llamar recursivamente a writeValue) con el valor que le diste, que acabas de recibir, desde tu modelo.
En otras palabras, a menos que dependa de que su componente cambie de alguna manera inmediatamente el valor que recibe en writeValue, y que pase esos cambios de vuelta a su modelo pero solo en la segunda y posteriores invocaciones de writeValue, está seguro de no llamar a onChange desde writeValue.
Vea el ejemplo en la documentación aquí: https://angular.io/api/forms/ControlValueAccessor
