Experts in Angular

Guias DetalhadoO Guia Estelar para Componentes Angular: Conteúdo Dinâmico com ng-content
Em Angular, muitas vezes você precisa criar componentes que atuam como contêineres para diferentes tipos de conteúdo. Aqui é onde a projeção de conteúdo com ng-content entra em cena, permitindo que você crie componentes reutilizáveis e dinâmicos.

O Guia Estelar para Componentes Angular: Conteúdo Dinâmico com ng-content

Imagine que você está construindo uma frota de naves espaciais, onde cada nave precisa ser flexível o suficiente para carregar diferentes tipos de carga. Da mesma forma, em Angular, muitas vezes você precisa criar componentes que atuam como contêineres para diferentes tipos de conteúdo. Aqui é onde a projeção de conteúdo com ng-content entra em cena, permitindo que você crie componentes reutilizáveis e dinâmicos.

Criando um Cartão Personalizável

Vamos imaginar que você quer criar um componente de cartão que possa exibir diferentes tipos de conteúdo, como texto, imagens ou até mesmo outros componentes.

import { Component } from '@angular/core';

@Component({
  selector: 'custom-card',
  template: '<div class="card-shadow"> <!-- o conteúdo do cartão vai aqui --> </div>',
})
export class CustomCard {
  /* ... */
}

O Marcador de Posição <ng-content>

Para indicar onde o conteúdo deve ser inserido no cartão, você usa o elemento <ng-content>. Ele funciona como um marcador de posição, dizendo ao Angular: “Insira o conteúdo aqui!”.

Vamos atualizar o nosso componente de cartão para usar <ng-content>:

import { Component } from '@angular/core';

@Component({
  selector: 'custom-card',
  template: '<div class="card-shadow"> <ng-content></ng-content> </div>',
})
export class CustomCard {
  /* ... */
}

O <ng-content> funciona de maneira semelhante ao elemento nativo <slot>, mas com algumas funcionalidades específicas do Angular.

O <slot> é um elemento do Shadow DOM que permite a inserção de conteúdo em componentes Web Components.

Ele é parte da especificação dos Web Components e serve para definir pontos de inserção de conteúdo no Shadow DOM.

Projeção de Conteúdo

Quando você usa um componente com <ng-content>, qualquer filho do elemento host do componente é renderizado ou projetado na localização do <ng-content>:

import { Component } from '@angular/core';

@Component({
  selector: 'custom-card',
  template: `
    <div class="card-shadow">
      <ng-content></ng-content>
    </div>
  `,
})
export class CustomCard {
  /* ... */
}

Usando o Componente

<custom-card>
  <p>Este é o conteúdo projetado</p>
</custom-card>

DOM Renderizado

<custom-card>
  <div class="card-shadow">
    <p>Este é o conteúdo projetado</p>
  </div>
</custom-card>

Conteúdo vs. Visualização

O Angular refere-se a qualquer filho de um componente passado dessa maneira como o conteúdo daquele componente. Isso é distinto da view do componente, que se refere aos elementos definidos no template do componente.
O conteúdo projetado são os elementos que você passa para o componente, enquanto a visualização são os elementos definidos no template do próprio componente.

Limitações e Considerações

  • Não é um Componente: O elemento <ng-content> não é um componente Angular nem um elemento do DOM. Ele é um marcador de posição especial que indica ao Angular onde renderizar o conteúdo.
  • Processamento em Tempo de Compilação: O compilador do Angular processa todos os elementos <ng-content> em tempo de compilação. Você não pode inseri-los, removê-los ou modificá-los em tempo de execução.
  • Sem Diretivas, Estilos ou Atributos: Você não pode adicionar diretivas, estilos ou atributos arbitrários ao elemento <ng-content>.
  • Sem Renderização Condicional: Você não deve usar diretivas como *ngIf, *ngFor ou ngSwitch para incluir condicionalmente o elemento <ng-content>. Para renderização condicional do conteúdo do componente, use fragmentos de template (veremos em um próximo artigo).

Com a projeção de conteúdo, você pode criar componentes flexíveis e reutilizáveis, capazes de se adaptar a diferentes tipos de conteúdo.

Múltiplos Portais de Entrada: Organizando o Conteúdo da sua Nave

A programação em Angular permite uma flexibilidade notável na criação de componentes dinâmicos e reutilizáveis. Pense em uma frota estelar onde diferentes compartimentos de uma nave são projetados para acomodar tipos específicos de carga. Da mesma forma, você pode projetar diferentes elementos de conteúdo em marcadores <ng-content>, utilizando seletores CSS para definir a organização interna dos componentes.

Utilizando Múltiplos Marcadores de Conteúdo

Vamos aprimorar nosso exemplo de componente de cartão, adicionando dois marcadores de conteúdo: um para o título do cartão e outro para o corpo do cartão. Utilizando o atributo select, podemos direcionar onde cada parte do conteúdo será inserida.

<div class="card-shadow">
  <ng-content select="card-title"></ng-content>
  <div class="card-divider"></div>
  <ng-content select="card-body"></ng-content>
</div>

Usando o atributo select, você pode especificar quais elementos serão projetados em cada portal de entrada. É como ter etiquetas em cada compartimento da nave, indicando o tipo de carga que ele aceita.

Utilizando o Componente
<custom-card>
  <card-title>Olá</card-title>
  <card-body>Bem-vindo ao exemplo</card-body>
</custom-card>

Neste exemplo, o elemento <card-title> será projetado no primeiro portal de entrada e o elemento <card-body> será projetado no segundo.

DOM Renderizado
<custom-card>
  <div class="card-shadow">
    <card-title>Olá</card-title>
    <div class="card-divider"></div>
    <card-body>Bem-vindo ao exemplo</card-body>
  </div>
</custom-card>

Capturando Elementos Não Correspondidos

Se o componente inclui um ou mais marcadores <ng-content> com um atributo select e também um marcador <ng-content> sem o atributo select, o último capturará todos os elementos que não correspondem aos seletores especificados.

<div class="card-shadow">
  <ng-content select="card-title"></ng-content>
  <div class="card-divider"></div>
  <!-- Captura todos os elementos que não são "card-title" -->
  <ng-content></ng-content>
</div>

Se você incluir um ou mais portais de entrada com o atributo select, mas também incluir um portal sem o atributo select, este último capturará todos os elementos que não corresponderem a nenhum dos outros portais. É como um compartimento “geral” para qualquer tipo de carga.

<custom-card>
  <card-title>Olá</card-title>
  <img src="imagem.jpg" />
  <p>Bem-vindo ao exemplo</p>
</custom-card>
DOM Renderizado
<custom-card>
  <div class="card-shadow">
    <card-title>Olá</card-title>
    <div class="card-divider"></div>
    <img src="imagem.jpg" />
    <p>Bem-vindo ao exemplo</p>
  </div>
</custom-card>

Neste caso, o elemento <img> e o elemento <p> serão projetados no portal padrão, pois não correspondem a nenhum seletor específico.

Conteúdo Não Projetado

Se um componente não tiver um portal padrão (sem o atributo select), qualquer elemento que não corresponder a nenhum dos outros portais não será renderizado no DOM. É como se a carga não coubesse em nenhum compartimento e fosse deixada para trás.

Alias para Projeção de Conteúdo

No universo do Angular, a flexibilidade na manipulação de conteúdo projetado é essencial para criar componentes reutilizáveis e altamente configuráveis. Às vezes, você pode precisar projetar conteúdo de um elemento que não corresponde diretamente ao seletor CSS esperado. Para esses casos, o Angular oferece o atributo especial ngProjectAs, que permite especificar um seletor CSS em qualquer elemento para fins de projeção.

Utilizando ngProjectAs

O atributo ngProjectAs permite que você defina um seletor CSS para um elemento, e quando o Angular verifica esse elemento contra um marcador <ng-content>, ele compara com o valor de ngProjectAs em vez da identidade do elemento.

Template do Componente

Vamos atualizar nosso componente de cartão para usar o atributo ngProjectAs:

import { Component } from '@angular/core';

@Component({
  selector: 'custom-card',
  template: `
    <div class="card-shadow">
      <ng-content select="card-title"></ng-content>
      <div class="card-divider"></div>
      <ng-content></ng-content>
    </div>
  `,
  styles: [`
    .card-shadow {
      box-shadow: 0 4px 8px rgba(0,0,0,0.1);
      padding: 16px;
      border-radius: 8px;
    }
    .card-divider {
      height: 1px;
      background: #e0e0e0;
      margin: 16px 0;
    }
  `]
})
export class CustomCardComponent { }

Usando o Componente com ngProjectAs

Vamos agora utilizar o CustomCardComponent e projetar conteúdo utilizando o atributo ngProjectAs.

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <custom-card>
      <h3 ngProjectAs="card-title">Olá</h3>
      <p>Bem-vindo ao exemplo</p>
    </custom-card>
  `
})
export class AppComponent { }
Resultado Renderizado

<custom-card>
  <div class="card-shadow">
    <h3>Olá</h3>
    <div class="card-divider"></div>
    <p>Bem-vindo ao exemplo</p>
  </div>
</custom-card>

Neste exemplo, o elemento <h3> é projetado na área card-title do componente CustomCardComponent devido ao atributo ngProjectAs, mesmo que o seletor CSS não corresponda diretamente ao elemento.

Limitações do ngProjectAs

  • Valores Estáticos: O ngProjectAs suporta apenas valores estáticos e não pode ser vinculado a expressões dinâmicas.
  • Uso Específico: É útil quando você precisa projetar elementos que não correspondem diretamente aos seletores definidos nos marcadores <ng-content>.

Benefícios do ngProjectAs

  1. Flexibilidade: Permite projetar conteúdo de maneira flexível sem modificar a estrutura do componente.
  2. Organização: Facilita a organização de conteúdo complexo, mantendo a clareza e a legibilidade do código.
  3. Reutilização: Aumenta a reutilização de componentes, permitindo que diferentes elementos sejam projetados conforme necessário.

Projeção de Conteúdo com ng-content: Exemplo Completo

Vamos consolidar tudo o que aprendemos sobre ng-content, criando um exemplo completo que abrange múltiplos marcadores de conteúdo e o uso do ngProjectAs para aliasing. Este exemplo mostra como criar um componente de cartão personalizado que pode projetar conteúdo de maneira flexível e organizada.