Artigo original: Everything you need to know about ng-template, ng-content, ng-container, and *ngTemplateOutlet in Angular

Era um dia daqueles em que eu me encontrava ocupado trabalhando em novos recursos de um projeto para o meu escritório. Foi aí que, de repente, algo chamou minha atenção:

1_mIQLobNDf0JSUgL7jfq9aQ
DOM final renderizado no Angular

Ao inspecionar o DOM, eu vi que ngcontent estava sendo aplicado nos elementos pelo Angular. Hmm… se eles contêm os elementos no DOM final, qual é a função do <ng-container>? Naquele momento, me senti confuso sobre a diferença entre <ng-container> e <ng-content>.

Ao tentar buscar respostas para minhas perguntas, descobri o conceito de <ng-template>. Para minha surpresa, também havia um *ngTemplateOutlet. Comecei minha jornada à iluminação sobre os dois conceitos e, agora, tinha quatro conceitos que pareciam a mesma coisa!

Você já esteve na mesma situação? Caso isso tenha acontecido com você, está no lugar certo. Sem mais delongas, vamos examinar um por um.

1. <ng-template>

Como o nome sugere, <ng-template> é um elemento de template (em português, modelo) que o Angular usa com as diretivas estruturais (*ngIf, *ngFor, [ngSwitch] e as diretivas personalizadas).

Esses elementos de modelo, ou template, funcionam apenas na presença de diretivas estruturais. O Angular envolve o elemento host (ao qual a diretiva é aplicada) dentro de <ng-template> e consome <ng-template> no DOM final substituindo-o por comentários de diagnóstico.

Considere um exemplo simples de *ngIf:

1_5QM2oe5GQVf7HATJxrX8Cw
Exemplo 1- processo do Angular de interpretação das diretivas estruturais

Acima, vemos a interpretação do Angular para *ngIf. O Angular coloca o elemento host, ao qual a diretiva é aplicada, dentro de <ng-template> e mantém o host como está. O DOM final é semelhante ao que vimos no começo deste artigo:

1_y2SVXFRl57rxi5wr-FzKvA
Exemplo 1- DOM final renderizado

Uso:

Vimos como o Angular usa <ng-template>, mas e se quiséssemos usá-lo? Como esses elementos funcionam apenas com uma diretiva estrutural, podemos escrever:

1_FLw4KCyW4vU1NupwP1z1uQ
Exemplo 2- usando <ng-template>

Aqui, home é uma propriedade boolean do componente definida com o valor true. O resultado do código acima no DOM é:

1_Iki7GXryxU_o9gCuGte0YA
Exemplo 2- DOM final renderizado

Nada foi renderizado! :(

Por que não conseguimos ver nossa mensagem mesmo depois de usar <ng-template> corretamente com uma diretiva estrutural?

Esse era o resultado esperado. Como já havíamos discutido, o Angular substitui <ng-template> por comentários de diagnóstico. Sem dúvidas, o código acima não geraria erros, pois o Angular está confortável com o caso de uso. Você jamais saberia o que aconteceu de fato internamente.

Vamos comparar os dois DOMs acima que foram renderizados pelo Angular:

1_y2SVXFRl57rxi5wr-FzKvA-1
1_Iki7GXryxU_o9gCuGte0YA-1
Exemplo 1 x Exemplo 2

Se olhar mais de perto, verá que existe uma tag de comentário extra no DOM final do Exemplo 2. O código que o Angular interpretou foi:

1_Nrmv3ivT8fB-h3qMS8gzkw
Processo de interpretação do Angular para o Exemplo 2

O Angular envolveu o <ng-template> de host dentro de outro <ng-template> e converteu não apenas o <ng-template> externo em comentários de diagnóstico, mas o interno também! É por isso que não foi possível ver sua mensagem.

Para se livrar disso, há duas maneiras de obter seu resultado desejado:

1_WSp1Iep84HFY9iOM1TLPDw
Uso correto do <ng-template>

Método 1:

Neste método, você fornece ao Angular um formato "sem rodeios", que não necessita de mais processamento. Neste caso, o Angular somente converteria <ng-template> em comentários, mas deixaria o conteúdo dentro dele intocado (O conteúdo já não está dentro de <ng-template> como estaria no caso anterior). Assim, ele será renderizado corretamente.

Para saber mais sobre como usar esse formato com outras diretivas estruturais, consulte este artigo (em inglês).

Método 2:

Este é um formado visto muito pouco e raramente usado (com dois <ng-template> irmãos). Aqui, damos uma referência de template a *ngIf em seu then para informar a ele qual template deve ser usado se a condição for verdadeira.

Usar vários <ng-template> assim não é recomendado (você pode usar <ng-container> em vez disso), já que não é para isso que ele serve. Ele é usado como um contêiner para templates que podem ser reutilizados em diversos locais. Trataremos mais sobre isso em uma seção posterior deste artigo.

2. <ng-container>

Você já escreveu ou viu algum código que se pareça com isso:

1_xSzfSSecltMEvHbwKoTlhQ
Exemplo 1

O motivo pelo qual muitos de nós escrevemos código assim é a incapacidade de usar diversas diretivas estruturais em um único elemento host no Angular. Agora, esse código funciona bem, mas introduz várias <div> adicionais vazias ao DOM se item.id for um valor falso que pode não ser obrigatório.

1_EZDOC5gDjhx0y-2pMGgP1A
Exemplo 1- DOM final renderizado

Pode ser que você não se preocupe com um exemplo simples como esse, mas, para uma aplicação maior, com um DOM mais complexo (exibindo dezenas de milhares de dados), isso pode passar a ser um problema, já que os elementos podem ter listeners associados a eles que ainda estarão lá, no DOM, escutando os eventos.

O que é pior: imagine o nível de aninhamento necessário para aplicar a estilização (CSS)!

1_sTllfe9eYy24VzWXEVZMew
Imagem extraída de: Inside Unbounce

Sem problemas. É quando <ng-container> aparece para salvar o dia!

O <ng-container> do Angular é um elemento de agrupamento que não interfere nos estilos ou no layout, pois o Angular não o coloca no DOM.

Assim, se escrevermos o Exemplo 1 com <ng-container>, teremos:

1_j-TJRTA11OrLKdLrmrjQjA
Exemplo 1 com <ng-container>

O DOM final seria:

1_7D-if7f35ct3vkY3AnozUQ
DOM final renderizado com <ng-container>

Percebeu que nos livramos das <div> vazias? Devemos usar o <ng-container> quando queremos apenas aplicar várias diretivas estruturais sem introduzir elementos extras ao DOM.

Para mais informações, consulte a documentação (em inglês). Existe um outro caso de uso, em que ele é usado para injetar um template dinamicamente em uma página. Trataremos desse caso de uso na última seção do artigo.

3. <ng-content>

<ng-content> é usado para criar componentes configuráveis. Isso significa que os componentes podem ser configurados, dependendo das necessidades do usuário. Essa situação é bem conhecida como projeção de conteúdo. Os componentes que são usados em bibliotecas publicadas fazem uso de <ng-content> para se tornar configuráveis.

Considere um componente <project-content> simples:

1_gzHVRbeW6JYv3XUxX5tQRA
Exemplo 1- definição de <project-content>
1_HIp3l46s5LRIPS8Cs3ZPlg
Projeção de conteúdo com o componente <project-content>

O conteúdo em HTML passado dentro das tags de abertura e de fechamento do componente <project-content> é o conteúdo a ser projetado. É a isso que chamamos de projeção de conteúdo. O conteúdo será renderizado dentro de <ng-content>, dentro do componente. Isso permite que o consumidor do componente <project-content> passe um footer personalizado dentro do componente e controle exatamente a maneira como ele quer que o footer seja renderizado.

Projeções múltiplas:

E se você pudesse decidir qual conteúdo deve ser colocado em qual posição? Em vez de todo conteúdo ser projetado dentro de um único <ng-content>, você também pode controlar a maneira como o conteúdo será projetado com o atributo select de <ng-content>. Ele recebe um seletor de elementos para decidir qual conteúdo deve projetar em um <ng-content> específico.

Aqui vemos como:

1_G6Ruc21MJctpiYqkdD5DjQ
Exemplo 2- diversas projeções de conteúdo com o <project-content> atualizado

Modificamos a definição de <project-content> para que realize diversas projeções de conteúdo. O atributo select decide o tipo de conteúdo que será renderizado dentro de um <ng-content> específico. Aqui, temos o primeiro select renderizando o elemento de título h1. Se o conteúdo projetado não tiver um elemento h1, nada será renderizado ali. Da mesma forma, o segundo select procura por uma div. O resto do conteúdo é renderizado dentro do último <ng-content> que não tem um select.

A chamado ao componente teria essa aparência:

1_rZar8_BvO5g53BQVJS36aQ
Exemplo 2- chamada ao componente <project-content> em um componente pai

4. *ngTemplateOutlet

Ele é usado como um contêiner para templates que podem ser reutilizados em diversos locais. Trataremos mais sobre isso em uma seção posterior deste artigo.

Existe um outro caso de uso, em que ele é usado para injetar um template dinamicamente em uma página. Trataremos desse caso de uso na última seção do artigo.

Esta é a seção onde discutiremos os dois pontos acima, mencionados anteriormente. *ngTemplateOutlet é usado para dois cenários — inserir um template comum em diversas seções de uma view, não importando os laços ou condições, e criar um componente altamente configurável.

Reutilização de um template:

Considere uma view em que você precise inserir um template em diversos lugares. Por exemplo, o logotipo de uma empresa em um site da web. Podemos conseguir isso escrevendo o template para o logotipo uma vez e reutilizando-o em todos os locais da view.

A seguir, vemos o trecho de código para isso:

1_M2mxgv1g3VcftdHOFFmTdw
Exemplo 1- reutilização do template

Como você pode ver, simplesmente escrevemos o template do logotipo uma vez e o utilizamos três vezes na mesma página com uma única linha de código!

*ngTemplateOutlet também aceita um objeto de contexto, que pode ser passado para personalizar o que é exibido por um template comum. Para obter mais informações sobre o objeto de contexto, consulte a documentação oficial (em inglês).

Componentes personalizáveis:

O segundo caso de uso para *ngTemplateOutlet é o dos componentes altamente personalizáveis. Considere nosso exemplo anterior do componente <project-content> com algumas modificações:

1_AwPv-pFH7e-Abhr-odvPyQ
Exemplo 2- tornando o componente project-content.html personalizável

Acima, vemos a versão modificada do componente <project-content>, que aceita três propriedades de entrada — headerTemplate, bodyTemplate e footerTemplate. A seguir, vemos o trecho para project-content.ts:

1_KHFWhtDmaysZMxGDTT_61Q
Exemplo 2- tornando o componente project-content.ts personalizável

O que estamos tentando conseguir aqui é mostrar os elementos header, body e footer do modo como são recebidos do componente pai de <project-content>. Se qualquer um deles não for fornecido, o componente exibirá o template padrão em seu lugar. Assim, criamos um componente altamente personalizável.

Para usarmos o componente que acabamos de modificar:

1_13rIyei1HqPdOsQ44l9tug
Exemplo 2- usando o componente que acaba de ser modificado, <project-content>

É assim que passaremos as referências do template para nosso componente. Se alguma delas não for passada, o componente renderizará o template padrão.

ng-content x *ngTemplateOutlet

Ambos nos ajudarão a obter componentes altamente personalizáveis, mas qual deles escolheremos e quando faremos isso?

Pode-se ver, claramente, que *ngTemplateOutlet nos dá uma capacidade maior de mostrar o template padrão se nenhum componente for fornecido.

Não é o que ocorre com ng-content. Ele renderiza o conteúdo como ele está. No máximo, é possível dividir o conteúdo e renderizá-lo em locais diferentes da view com a ajuda do atributo select. Não é possível renderizar condicionalmente o conteúdo dentro de ng-content. Você precisa mostrar o conteúdo que é recebido do elemento pai sem poder decidir com base no conteúdo.

Porém, a escolha de um ou de outro depende totalmente de seu caso de uso. Pelo menos, agora, você tem uma nova ferramenta, *ngTemplateOutlet, que poderá usar para ter mais controle sobre o conteúdo, juntamente com os recursos de ng-content!