Cuando un componente incluye campos de formulario, WCF implementa la API ControlValueAccesor de Angular que permite incluir el componente completo como un campo más de un FormGroup.
ControlValueAccessor
ControlAvalueAccesor es una API estándar de Angular, que actúa como un puente entre la API de formularios de Angular y los elementos nativos que el DOM crea para los formularios. Es la API recomendada para permitir incluir un componente Angular en un formulario como si fuera un campo más, y es la que utiliza WCF para conseguir este objetivo, haciendo mucho más fácil la creación y manejo de formularios que contienen componentes.
Analicemos el siguiente formulario generado por Web Component Factory, con una captura tomada de la app-demo:

El código que incluye la app-demo, que abarca el formulario completo como un FormControl es el siguiente:
typescript:
formAppDemo = new FormGroup({
formEjemploForm: new FormControl({
inputDeTexto: "",
checkArriba: false,
checkAbajo: false,
comboBox: "",
radioButton: ""
})
});
HTML en el template:
<div [formGroup]="formAppDemo">
<comp-form-ejemplo formControlName="formEjemploForm"></comp-form-ejemplo>
</div>
La magia de Angular está lista: todos los campos están vinculados (binded) y las variables se van a actualizar con las interacciones. Por ejemplo, la propia app-demo despliega los valores incluyendo en la plantilla una sola línea de código:
<pre>{{ formAppDemo.controls.formEjemploForm.value | json }}</pre>
Leyendo y asignando valores al formulario y sus campos
Desde el componente que implementa el formulario, leer los valores es muy sencillo, dado que se comporta como cualquier formulario estándar de Angular. En el ejemplo el componente que implementa el formulario es app-demo y el FormGroup se llama formAppDemo, por lo que podemos acceder a sus valores de la siguiente forma:
Typescript:
console.log(this.formAppDemo.controls.formEjemploForm.value);
Salida en la consola:
▶ Object { inputDeTexto: "de los Palotes", checkArriba: true,
checkAbajo: false, comboBox: "2", radioButton: "3" }
Asignando valores
Como en cualquier formulario, los valores del FormControl se pueden asignar con la granularidad que se desee. En definitiva es un objeto, y solo hay que generar el json acorde con la estructura.
Todas las asignaciones que se muestran funcionan sin problemas. Para asignar valores a los campos del formulario se utiliza el método estándar setValue. En nuestro ejemplo de la app-demo:
this.formAppDemo.controls.formEjemploForm.setValue(
{
inputDeTexto: "Zinedine Zidane",
checkArriba: true,
checkAbajo: false,
comboBox: "1",
radioButton: "4"
}
);
Cambia los valores, tal como era de esperar.

Asignando solo algunos valores
Como es razonable esperar, se pueden asignar solamente algunos campos del formulario, pero dado el carácter fuertemente tipeado de Typescript y la característica asincrónica de JavaScritp, es necesario tener algunas precauciones, porque de lo contrario pueden borrarse el resto de los valores.
El código recomendado, siempre para nuestro ejemplo, es el siguiente:
const currentValue = this.formAppDemo.controls.formEjemploForm.value;
queueMicrotask(() => {
this.formAppDemo.controls.formEjemploForm.patchValue(
{
...currentValue,
checkAbajo: false,
radioButton: "1"
} as any
);
});
Los detalles importantes:
currentValue: permite preservar completo el valor de la variable que contiene el formulario. Si ella, elpatchValuefunciona correctamente pero la variable pierde las propiedades que no fueron actualizadas.
El motor de Angular las recupera automáticamente en la próxima operación sobre el formulario, pero es un comportamiento molesto, que es fácil evitar y vale la pena hacerlo.queueMicrotask: es una función estándar de JavaScript que encola la función invocada al final de la cola de ejecución. El funcionamiento sigue siendo asincrónico, pero evita que funciones muy cortas terminen antes que funciones muy largas.
En este caso, si el formulario es muy grande, la inicialización del formulario terminará antes que elpatchValue, obteniendo el resultado no deseado de un formulario que tiene las propiedades de la segunda función asignadas y el resto vacías.as any:como estamos asignado parcialmente, y a pesar delcurrentValueque se describe más arriba, los tipos no son exactamente iguales y obtenemos un error al compilar. Agregaras anyal finalizar el objeto elimina este error.
Binding de eventos
Un formulario Angular no tiene valor alguno si no se puede vincular (bind) el manejo de eventos para reaccionar adecuadamente.
Esto se puede hacer agregando los tradicionales paréntesis curvos en el template html del formulario. Sin embargo, la recomendación para los reactiveForms de Angular es hacerlo con la directiva host: en el decorador del componente. Esto brinda control absoluto del comportamiento sin necesidad de alterar el HTML cada vez.
Siempre utilizando el ejemplo, asociemos los eventos click y focusout al formulario. Para ello es necesario en el decorador agregar el atributo hosts, y en el código que se exporta, agregar las funciones asociadas.
El decorador del componente
@Component({
selector: "app-demo",
standalone: true,
imports: [FormEjemplo, ReactiveFormsModule, JsonPipe],
templateUrl: "app-demo.component.html",
host: {
"(click)": "onClick($event)",
"(focusout)": "onFocusout($event)"
}
})
En el código exportable
onClick(event: MouseEvent) {
console.log("click event", event);
}
onFocusout(event: FocusEvent) {
console.log("focus event", event);
}
Utilizando el atributo hosts: se puede asociar (bind) cualquier evento asociable de Angular.
Un detalle no menor es que al asociarlo en el componente padre, la función asociada será invocada para todos los eventos del componente y no solo para los del formulario. Esto se puede resolver fácilmente extendiendo el componente del formulario para agregarle las funciones asociadas.
Formularios anidados
Los componentes generados con WCF pueden tener formularios que incluyen sub-componentes que contienen a su vez formularios, con cualquier profundidad.
A través de la implementación de ControlValueAccessor WCF va construyendo una estructura JSON que alberga todos los campos de los distintos componentes y que terminará participando en el FormGroup del componente padre como un campo más.
Veamos un ejemplo:

Desde el punto de vista del formulario, el componente, con sus dos niveles de anidación se comporta como un campo que tiene una estructura compuesta. Para incluirlo y controlar los valores, se hace exactamente igual que con un formulario incluido en un componente que no tenga anidaciones:
formAppDemo = new FormGroup({
formAnidadoForm: new FormControl({
motivo: "",
soyCliente: false,
yaFuiSucursal: false,
nombreCompleto: {
nombre: "",
apellido: ""
},
direccion: {
calle: "",
numero: "",
apartamento: "",
deptoYCiudad: {
ciudad: "",
departamento: ""
},
},
})
});
Y de la misma manera que en un formulario común se puede se puede asignar los valores default, asignar programáticamente los campos de forma total o parcial, operar con sus valores y vincular (binding) los eventos que se deseen.
Modificar los valores usando el servicio wcfSubcomps
Todos los campos de un formulario, independientemente de si es o no anidado y de los niveles de anidación, Sse pueden modificar desde cualquier componente utilizando el servicio wcfSubcomps.
El servicio tiene previsto el problema de las asignaciones parciales, por lo que alcanza con asignar los campos que se desee, sin preocuparse de los demás campos, que permanecerán inalterados.
En el ejemplo anterior, se podrían modificar algunos campos del formulario invocando el servicio de esta forma:
this.wcfService.changeAttribute({"id":"form-anidado", "formAnidadoForm":
{
"motivo": "El mejor gol del mundo",
"direccionCompuesta": {
"deptoYCiudad": {
"ciudad": "La Boca",
}
}
}
});
Los componentes están diseñados para transmitirse unos a otros las modificaciones cuando ocurren, por lo que se puede modificar directamente un campo de un componente de los que forman el grupo, y esto se reflejará en el conjunto.
Este código:
this.wcfService.changeAttribute({"id":"form-anidado.direccion-compuesta.depto-y-ciudad", "deptoYCiudadForm":
{
"ciudad": "La Boca soy yo",
}
});
Genera exactamente el mismo resultado que este código:
this.wcfService.changeAttribute({"id":"form-anidado", "formAnidadoForm":
{
"direccionCompuesta": {
"deptoYCiudad": {
"ciudad": "La Boca",
}
}
}
});
Elegir uno u otro dependerá de las circunstancias y el contexto, que determinarán qué es más conveniente.
Leave a Reply