El laberinto de la construcción manual (Pre-Angular 20)
Antes de Angular 20, trabajar con componentes creados dinámicamente era engorroso. El "enlace de datos bidireccional" (two-way data binding) era muy incómodo, y no existía un estilo unificado para trabajar con "entradas" (inputs) y "salidas" (outputs).
Para configurar un componente dinámico, era como si tuviéramos que conectar cada componente (input) y cable de salida (output) de forma individual y a mano. Para un hipotético NotificationComponent, los pasos manuales e imperativos eran:
- Conexión manual de componentes: Se tenía que usar
setInput()para conectar cada componente (propiedad) por separado. - Preparación manual de los datos: Si los datos venían de una "señal" (signal), debían ser desenvueltos (acceder a su valor) para obtener el valor real y no la función.
- Gestión de las conexiones de salida: Se requería suscribirse manualmente a las salidas (outputs), guardando cada suscripción. Era crucial recordar desconectar estos cables (unsubscribe) para prevenir fugas de memoria.
- Sincronización manual de datos: Para que el componente dinámico reaccionara a los cambios en una signal del padre, era necesario un "efecto" (effect) para rastrear el cambio y luego volver a conectar manualmente el cable (
setInput) con el nuevo valor.
El código era como un diagrama de cableado muy imperativo y no reactivo:
La forma de acceder a la salida (output) requería acceder directamente a la propiedad de la clase, lo que no respetaba el "alias" de la salida y era diferente de cómo funciona en las plantillas.
El panel de control unificado (Angular 20)
A partir de Angular 20, la configuración de componentes dinámicos se gestiona a través de la nueva propiedad bindings. Este nuevo enfoque es declarativo y reactivo, y su corazón es la "referencia del contenedor de vista" (ViewContainerRef), que actúa como el "socket" principal donde se insertará el nuevo componente.
El poder de ViewContainerRef
Una "referencia del contenedor de vista" (ViewContainerRef) es el "socket" en el chasis de tu PC donde se puede instalar una tarjeta de video o de sonido. Permite crear, eliminar o incluso mover "tarjetas" (views) dentro de ese contenedor. Para acceder a él, se puede inyectar con inject() o, para una ubicación específica, usar una referencia de plantilla.
Para controlar la ubicación exacta de tu componente dinámico, puedes añadir un <ng-container> en tu plantilla para definir un lugar de inserción:
Luego, en el código TypeScript, se accede a esa ubicación específica usando viewChild:
Ahora, la creación del componente dinámico con createComponent se realiza directamente en ese contenedor, asegurando que se renderice en el lugar deseado.
Configuración con bindings
La propiedad bindings permite definir "enlaces de entrada" (input), "enlaces de salida" (output) y "enlaces bidireccionales" (two-way bindings) en un estilo unificado. Esto elimina la necesidad de setInput() manuales y de suscripciones.
Aquí está el código completo para un ejemplo práctico, como un DialogComponent, que ahora se conecta con un solo "conector modular":
La nueva API gestiona automáticamente la limpieza de suscripciones cuando el componente es destruido, eliminando las pesadillas de gestión de memoria y los cables sueltos.
Las tres funciones de enlace en detalle
La API de "enlaces" (bindings) se compone de tres funciones esenciales que deben importarse desde @angular/core.
1. inputBinding()
Esta función se utiliza para conectar las "entradas" (inputs) del componente.
Cuando se proporciona un signal directamente, inputBinding accede automáticamente al valor de la signal y rastrea reactivamente los cambios, actualizando la entrada del componente sin necesidad de un effect manual.
Ejemplos de inputBinding:
2. outputBinding()
Esta función maneja los "eventos de salida" (outputs) emitidos por el componente dinámico.
Se puede especificar el tipo de valor emitido usando un "tipo genérico" (generic type) para mejorar la seguridad de tipos en TypeScript:
3. twoWayBinding()
Esta es la solución simplificada para el "enlace de datos bidireccional" (two-way data binding), reemplazando el cableado manual complejo.
Simplemente se define el nombre de la "entrada modelo" (input model) y se proporciona la signal del componente padre que se desea mantener sincronizada.
Angular maneja automáticamente la convención de sufijo Change (por ejemplo, isUrgent se empareja con la salida isUrgentChange).
Directivas aplicadas en tiempo de ejecución
Angular 20 también permite añadir componentes o características adicionales (directives) al componente creado dinámicamente. Esto se logra mediante la propiedad directives en la configuración de createComponent.
Para directivas sencillas, se proporciona el nombre del constructor de la directiva. Para directivas más complejas que requieren configuración, se proporciona un objeto que incluye la clave type (el constructor de la directiva) y la propiedad bindings para configurar sus propias entradas.
Ejemplo de directivas de runtime:
Configuraremos un NotificationComponent con una directiva simple (FocusHighlightDirective) y una directiva compleja de la librería Material (SnackbarDirective) para mostrar mensajes de estado al pasar el ratón:
Esta capacidad permite un diseño más modular y una configuración dinámica de la experiencia del usuario.
En resumen, el nuevo enfoque de Angular 20 hace que la creación de componentes dinámicos sea más consistente, limpia y legible, ya que la sintaxis de "enlace" (binding) ahora es idéntica a la de las plantillas de Angular, además de ofrecer reactividad incorporada y gestión simplificada de la memoria. Ahora, en lugar de ensamblar una PC a mano, utilizas un "panel de control" unificado para la configuración y conexión de tus componentes.
Bonus: Stackblitz Demo Github Repo