Construindo componentes mais flexíveis com ngTemplateOutlet - Angular 2+
Reaproveitamento de código com certeza é uma das preocupações de todo desenvolvedor. Quando criamos um componente, a ideia de compartilhá-lo em outros projetos pode trazer economia de tempo e dinheiro.
Eventualmente temos problemas quando tentamos utilizar um componente que quase "encaixa" na nossa necessidade. Por exemplo, suponha que você tenha um componente que renderize um card conforme a imagem abaixo, porém sua necessidade é um pouco diferente:
Como este cenário é muito comum, podemos recorrer ao <ng-template>
e <ng-container>
para deixar nossos componentes mais customizáveis.
Componente Card
Vamos direto ao código para mostrar como o componente card foi estruturado:
Utilizando o componente:
Customização com ngTemplateOutlet
Veja que o componente acima não permite que o desenvolvedor customize a parte onde a data é renderizada (canto superior direito). Vamos tomar como objetivo a alteração do componente de modo que seja possível customizar esta região.
Fazendo uma analogia, nosso componente é como se fosse a moldura de um quadro, você pode simplesmente troca a tela para obter um outro resultado mantendo os aspectos originais da moldura, ou seja, o "componente" moldura permite a customização do seu "template" (tela).
Como primeiro passo vamos isolar em um template (<ng-template>
) a responsabilidade de renderizar uma data já formatada:
<ng-template #header let-data="dataFormatada">
{{data}}
</ng-template>
Veja que este template é identificado através da marcação #header
e recebe um parâmetro chamado dataFormatada
. O valor do dataFormatada
é atribuído à variável data
.
No lugar do trecho {{data | date}}
vamos adicionar um <ng-container>
:
<div class="card">
<div class="titulo">
<div>{{titulo}}</div>
<div>
<!--
Como "jogar" o template aqui
no container?
-->
┌→ <ng-container>
| </ng-container>
| </div>
| </div>
| <div class="conteudo">{{conteudo}}</div>
| </div>
|
└→ <ng-template #header let-data="dataFormatada">
{{data}}
</ng-template>
Agora vamos utilizar algumas propriedades do <ng-container>
para "dizer" ao <ng-container>
que renderize o <ng-template #header>
:
[ngTemplateOutlet]
recebe o template que será renderizado. No nosso caso será o templateheader
[ngTemplateOutletContext]
envia um objeto (contexto) para o template. Neste caso vamos passar a data já formatada na variáveldataFormatada
. O templateheader
já está preparado para receber e tratar esta variável.
Pronto! com esta pequena refatoração isolamos a área responsável por renderizar a data no canto superior direito. Porém, nada mudou no resultado final, ainda! veja na imagem abaixo o resultado até este momento:
Importante: perceba que o <ng-container>
pode receber qualquer template. Esta refatoração foi importante justamente por este motivo. Agora podemos passar um template que assumirá a responsabilidade de renderizar a data no canto superior direito.
Trazendo isto para o código, teríamos a seguinte situação:
No código acima estamos passando um <ng-template>
para dentro do app-card
, porém o componente ainda não está preparado para receber um <ng-template>
externo. Então para identificar a presença de um <ng-template>
, podemos utilizar o @ContentChild
e convencionar um identificador (como um id
) padrão para este <ng-template>
externo:
Agora nosso componente consegue identificar a presença de um <ng-template>
identificado com o valor #tmpHeader
. Então podemos passar um template customizado da seguinte forma:
<app-card
[data]="dataAtual"
titulo="Card Exemplo 4"
conteudo="Card Teste">
<!--
Template para customizar a renderização
do canto superior direito
-->
<ng-template #tmpHeader let-data="dataFormatada">
Meu template: {{data}}
</ng-template>
</app-card>
Para finalizar, precisamos "dizer" ao <ng-container>
(dentro do template do componente) que se a variável headerCustomizado
tiver um valor, ou seja, houver um <ng-template #tmpHeader>
, ela (template externo) deverá ser utilizada para renderizar o canto superior direito do card. Caso contrário, o <ng-template #header>
(dentro do template do próprio componente) deverá ser utilizado.
O código ficará da seguinte forma:
Resultado:
Veja que gora podemos passar qualquer template para o componente de modo a customizar o trecho onde a data é renderizada. Abaixo há alguns exemplos:
Considerações
Com o entendimento do <ng-container>
e ngTemplateOutlet
podemos construir componentes mais flexíveis e aumentar o reaproveitamento de código. Inclusive, em um post um pouco mais antigo utilizamos este recurso para criar um componente no estilo tabela.
Código fonte:
Links interessantes: