Todo lo que necesitas saber sobre ng-template, ng-content, ng-container y *ngTemplateOutlet en Angular

Jul 1, 2021
admin

Fue uno de esos días en los que estaba ocupado trabajando en nuevas funcionalidades para mi proyecto de oficina. De repente, algo me llamó la atención:

DOM final renderizado en Angular

Mientras inspeccionaba el DOM vi los ngcontent que aplica Angular sobre los elementos. Hmm… si contienen los elementos en el DOM final, entonces ¿para qué sirve <ng-container>? En ese momento me confundí entre <ng-container> y <ng-content>.

En la búsqueda de las respuestas a mis preguntas descubrí el concepto de <ng-template>. Para mi sorpresa, también existía *ngTemplateOutlet. Empecé mi viaje buscando claridad sobre dos conceptos, pero ahora tenía cuatro, que sonaban casi igual.

¿Has estado alguna vez en esta situación? Si es así, entonces estás en el lugar correcto. Así que sin más preámbulos vamos a verlos uno a uno.

<ng-template>

Como su nombre indica el <ng-template> es un elemento plantilla que Angular utiliza con directivas estructurales (*ngIf, *ngFor, y directivas personalizadas).

Estos elementos plantilla sólo funcionan en presencia de directivas estructurales. Angular envuelve el elemento anfitrión (al que se aplica la directiva) dentro de <ng-template> y consume el <ng-template> en el DOM terminado sustituyéndolo por comentarios de diagnóstico.

Considere un ejemplo sencillo de *ngIf:

Ejemplo 1- Proceso de Angular de interpretación de directivas estructurales

Se muestra arriba la interpretación de Angular de *ngIf. Angular pone el elemento anfitrión al que se aplica la directiva dentro de <ng-template> y mantiene el anfitrión tal cual. El DOM final es similar al que hemos visto al principio de este artículo:

Ejemplo 1- DOM final renderizado

Uso:

Hemos visto cómo Angular utiliza <ng-template> pero ¿qué pasa si queremos utilizarlo? Como estos elementos funcionan sólo con una directiva estructural, podemos escribir como:

Ejemplo 2- Usando <ng-template>

Aquí home es una propiedad boolean del componente establecida con el valor true. La salida del código anterior en DOM:

Ejemplo 2- DOM final renderizado

¡No se ha renderizado nada!

¿Pero por qué no podemos ver nuestro mensaje incluso después de usar <ng-template> correctamente con una directiva estructural?

Este era el resultado esperado. Como ya hemos comentado, Angular sustituye el <ng-template> por comentarios de diagnóstico. Sin duda el código anterior no generaría ningún error, ya que Angular se ajusta perfectamente a su caso de uso. Nunca llegarías a saber qué ocurrió exactamente entre bastidores.

Comparemos los dos DOMs anteriores que fueron renderizados por Angular:

Ejemplo 1 vs Ejemplo 2

Si te fijas bien, hay una etiqueta de comentario extra en el DOM final del Ejemplo 2. ¡El código que Angular interpretó fue:

Proceso de interpretación de Angular para el Ejemplo 2

Angular envolvió su <ng-template> anfitrión dentro de otro <ng-template> y convirtió no sólo el <ng-template> exterior en comentarios de diagnóstico sino también el interior! Por eso no pudiste ver ninguno de tus mensajes.

Para librarte de esto hay dos formas de obtener el resultado deseado:

Uso correcto de <ng-template>

Método 1:

En este método, estás proporcionando a Angular el formato desazufrado que no necesita más procesamiento. Esta vez Angular sólo convertiría <ng-template> en comentarios pero deja el contenido dentro de él intacto (ya no están dentro de ningún <ng-template> como en el caso anterior). Así, renderizará el contenido correctamente.

Para saber más sobre cómo usar este formato con otras directivas estructurales consulta este artículo.

Método 2:

Este es un formato bastante poco visto y poco utilizado (usando dos <ng-template> hermanos). Aquí estamos dando una referencia de plantilla a la *ngIf en su then para decirle qué plantilla se debe utilizar si la condición es verdadera.

No se aconseja utilizar múltiples <ng-template> como este (se podría utilizar <ng-container> en su lugar) ya que no es para lo que están destinados. Se utilizan como un contenedor de plantillas que pueden ser reutilizadas en múltiples lugares. Cubriremos más sobre esto en una sección posterior de este artículo.

<ng-container>

¿Has escrito o visto alguna vez un código parecido a este:

Ejemplo 1

La razón por la que muchos de nosotros escribimos este código es la incapacidad de utilizar múltiples directivas estructurales en un solo elemento anfitrión en Angular. Ahora este código funciona bien pero introduce varios <div> vacíos extra en el DOM si item.id es un valor falso que podría no ser necesario.

Ejemplo 1- DOM final renderizado

Uno puede no preocuparse por un ejemplo simple como este pero para una aplicación enorme que tiene un DOM complejo (para mostrar decenas de miles de datos) esto puede llegar a ser problemático ya que los elementos pueden tener listeners adjuntos que seguirán ahí en el DOM escuchando eventos.

¡Lo que es aún peor es el nivel de anidamiento que tienes que hacer para aplicar tu estilo (CSS)!

Imagen de: Inside Unbounce

¡No te preocupes, tenemos <ng-container> al rescate!

El Angular <ng-container> es un elemento de agrupación que no interfiere con los estilos o el diseño porque Angular no lo pone en el DOM.

Así que si escribimos nuestro Ejemplo 1 con <ng-container>:

Ejemplo 1 con <ng-container>

Obtenemos el DOM final como:

DOM final renderizado con <ng-container>

Veamos que nos libramos de esos <div>s vacíos. Debemos usar <ng-container> cuando sólo queremos aplicar múltiples directivas estructurales sin introducir ningún elemento extra en nuestro DOM.

Para más información consulta la documentación. Hay otro caso de uso en el que se utiliza para inyectar una plantilla dinámicamente en una página. Cubriré este caso de uso en la última sección de este artículo.

<ng-content>

Se utilizan para crear componentes configurables. Esto significa que los componentes pueden ser configurados dependiendo de las necesidades de su usuario. Esto es bien conocido como Proyección de Contenido. Los componentes que se utilizan en las bibliotecas publicadas hacen uso de <ng-content> para hacerse configurables.

Considere un simple componente <project-content>:

Ejemplo 1- Definición de <proyecto-contenido>
Proyección de contenido con el componente <proyecto-contenido>

El contenido HTML que se pasa dentro de las etiquetas de apertura y cierre del componente <project-content> es el contenido a proyectar. Esto es lo que llamamos Proyección de Contenido. El contenido será renderizado dentro del <ng-content> dentro del componente. Esto permite al consumidor del componente <project-content> pasar cualquier pie de página personalizado dentro del componente y controlar exactamente cómo quiere que se renderice.

Proyecciones múltiples:

¿Qué pasaría si pudiera decidir qué contenido debe colocarse dónde? En lugar de que cada contenido se proyecte dentro de un único <ng-content>, también puedes controlar cómo se proyectará el contenido con el atributo select de <ng-content>. Se necesita un selector de elementos para decidir qué contenido se proyecta dentro de un <ng-content> en particular.

Así es como:

Ejemplo 2- Proyección de múltiples contenidos con <proyección de contenidos>

Hemos modificado la definición de <project-content> para realizar la proyección de múltiples contenidos. El atributo select selecciona el tipo de contenido que se renderizará dentro de un <ng-content> concreto. Aquí tenemos primero select para renderizar el elemento h1 de cabecera. Si el contenido proyectado no tiene ningún elemento h1 no renderizará nada. Del mismo modo, el segundo select busca un div. El resto del contenido se renderiza dentro del último <ng-content> sin select.

La llamada al componente tendrá el siguiente aspecto:

Ejemplo 2- Llamada al componente <project-content> en el componente padre

*ngTemplateOutlet

…Se utilizan como contenedor de plantillas que pueden ser reutilizadas en múltiples lugares. Cubriremos más sobre esto en una sección posterior de este artículo.
…Hay otro caso de uso donde se utiliza para inyectar una plantilla dinámicamente en una página. Cubriré este caso de uso en la última sección de este artículo.

Esta es la sección donde discutiremos los dos puntos mencionados anteriormente. *ngTemplateOutletSe utiliza para dos escenarios – para insertar una plantilla común en varias secciones de una vista independientemente de los bucles o la condición y para hacer un componente altamente configurado.

Reutilización de la plantilla:

Considere una vista en la que tiene que insertar una plantilla en varios lugares. Por ejemplo, el logotipo de una empresa a colocar dentro de una página web. Podemos lograrlo escribiendo la plantilla para el logotipo una vez y reutilizándola en todas partes dentro de la vista.

El siguiente es el fragmento de código:

Ejemplo 1- Reutilización de la plantilla

¡Como puede ver, sólo escribimos la plantilla del logotipo una vez y la utilizamos tres veces en la misma página con una sola línea de código!

*ngTemplateOutlettambién acepta un objeto de contexto que se puede pasar para personalizar la salida de la plantilla común. Para más información sobre el objeto de contexto consulte la documentación oficial.

Componentes personalizables:

El segundo caso de uso de *ngTemplateOutlet son los componentes altamente personalizados. Considere nuestro ejemplo anterior de <project-content>componente con algunas modificaciones:

Ejemplo 2- Haciendo componente personalizable, project-content.html

Arriba está la versión modificada del componente <project-content> que acepta tres propiedades de entrada – headerTemplate, bodyTemplate, footerTemplate. A continuación se muestra el fragmento para project-content.ts:

Ejemplo 2- Creación de un componente personalizable, project-content.ts

Lo que estamos tratando de lograr aquí es mostrar la cabecera, el cuerpo y el pie de página tal y como se reciben del componente padre de <project-content>. Si alguno de ellos no se proporciona, nuestro componente mostrará la plantilla por defecto en su lugar. Creando así un componente altamente personalizado.

Para usar nuestro componente recientemente modificado:

Ejemplo 2- Usando el componente recientemente modificado <project-content>

Así es como vamos a pasar las refs de plantilla a nuestro componente. Si no se pasa ninguna de ellas entonces el componente renderizará la plantilla por defecto.

ng-content vs. *ngTemplateOutlet

Ambos nos ayudan a conseguir componentes muy personalizados pero ¿cuál elegir y cuándo?

Se puede ver claramente que *ngTemplateOutlet nos da algo más de poder de mostrar la plantilla por defecto si no se proporciona ninguna.

Este no es el caso de ng-content. Renderiza el contenido tal cual. Como máximo puedes dividir el contenido y renderizarlo en diferentes lugares de tu vista con la ayuda del atributo select. No se puede renderizar condicionalmente el contenido dentro de ng-content. Usted tiene que mostrar el contenido que se recibe desde el padre sin medios para tomar decisiones basadas en el contenido.

Sin embargo, la elección de seleccionar entre los dos depende completamente de su caso de uso. Al menos ahora tenemos una nueva arma *ngTemplateOutlet en nuestro arsenal que proporciona más control sobre el contenido además de las características de ng-content!

Deja una respuesta

Tu dirección de correo electrónico no será publicada.