Artigo original: https://www.freecodecamp.org/news/everything-you-need-to-know-about-ng-template-ng-content-ng-container-and-ngtemplateoutlet-4b7b51223691/
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:

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
:

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:

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:

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

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:


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:

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:

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:

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.

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)!

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:

O DOM final seria:

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:


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:

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:

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:

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:

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
:

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:

É 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
!