Capítulo 08: Encapsulamento de CSS e Content Projection com ng-content
Capítulo 08: Encapsulamento de CSS e Content Projection com ng-content
Especialização em Front-End com Angular • Angular 18+ • Signals, Standalone Architecture, RxJS, NgRx e SSR
🗺️ Mapa Conceitual de Encapsulamento e Projeção de Conteúdo
flowchart TD
ParentTemplate["Template do Componente Pai"] -->|"Passagem de nós HTML via Slots"| SlotProjection["Mecanismo de Content Projection"]
subgraph PROJECTION["Projeção de Conteúdo (<ng-content>)"]
S1["Single-Slot: <ng-content />"]
S2["Multi-Slot: <ng-content select='[card-header]' />"]
S3["Fallback Content: <ng-content>Conteúdo Padrão</ng-content>"]
end
SlotProjection --> PROJECTION
subgraph ENCAPSULATION["Estratégias de ViewEncapsulation"]
E1["Emulated (Padrão: _ngcontent-cXXX)"]
E2["ShadowDom (Web Components Nativo)"]
E3["None (Estilos Globais no Header)"]
end
PROJECTION --> OutputDOM["Árvore DOM Final Estilizada com Segurança"]
ENCAPSULATION --> OutputDOM
style ParentTemplate fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style PROJECTION fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style ENCAPSULATION fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style OutputDOM fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
🏛️ 1. Encapsulamento de Estilos no Angular 18+
No ecossistema Angular, cada componente possui um escopo isolado de estilização CSS através do enum ViewEncapsulation. Esse mecanismo impede que classes com nomes comuns (como .btn, .card, .title) vazem e conflitem com outros componentes da aplicação.
1.1 As Três Estratégias de Encapsulamento
ViewEncapsulation.Emulated(Padrão):- O Angular injeta atributos únicos no HTML compilado (ex:
_nghost-ng-c123e_ngcontent-ng-c123) e reescreve os seletores CSS (ex:.card[_ngcontent-ng-c123]). - Não usa Shadow DOM real, garantindo compatibilidade universal e alta performance.
- O Angular injeta atributos únicos no HTML compilado (ex:
ViewEncapsulation.ShadowDom:- O Angular isola o componente dentro de uma Shadow Root nativa do navegador.
- CSS global não penetra no componente e o CSS interno não vaza de forma absoluta.
ViewEncapsulation.None:- O CSS declarado no componente é injetado diretamente na tag
<head>como estilo global.
- O CSS declarado no componente é injetado diretamente na tag
import { Component, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-painel-alerta',
standalone: true,
encapsulation: ViewEncapsulation.Emulated, // Estratégia padrão recomendada
styles: [`
:host {
display: block;
border: 1px solid #ccc;
border-radius: 8px;
padding: 1rem;
}
:host(.alerta-critico) {
border-color: #f44336;
background-color: #ffebee;
}
.titulo {
font-weight: 700;
color: #333;
}
`],
template: `
<div class="titulo">Alerta do Sistema</div>
<ng-content />
`
})
export class PainelAlertaComponent {}
📦 2. Projeção de Conteúdo com <ng-content>
A projeção de conteúdo permite construir componentes altamente reutilizáveis (como modais, cards, layouts de abas e painéis colapsáveis), onde o componente pai decide quais elementos internos serão exibidos.
2.1 Multi-Slot Content Projection (Slots Nomeados)
Você pode segmentar onde cada elemento do pai deve aterrissar utilizando o atributo select com seletores CSS (classes, tags ou atributos):
import { Component } from '@angular/core';
@Component({
selector: 'app-modal-dialog',
standalone: true,
styles: [`
.modal-box { background: white; border-radius: 8px; padding: 20px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); }
.modal-header { border-bottom: 1px solid #eee; padding-bottom: 10px; font-size: 1.25rem; font-weight: bold; }
.modal-body { padding: 15px 0; }
.modal-footer { border-top: 1px solid #eee; padding-top: 10px; display: flex; justify-content: flex-end; gap: 8px; }
`],
template: `
<div class="modal-box">
<header class="modal-header">
<ng-content select="[slot-header]" />
</header>
<section class="modal-body">
<ng-content select="[slot-body]" />
</section>
<footer class="modal-footer">
<!-- Angular 18: Conteúdo Fallback caso o pai não forneça ações -->
<ng-content select="[slot-footer]">
<button type="button" class="btn-padrao">Fechar Padrão</button>
</ng-content>
</footer>
</div>
`
})
export class ModalDialogComponent {}
2.2 Consumo no Componente Pai
<app-modal-dialog>
<h3 slot-header>Confirmar Exclusão de Registro</h3>
<div slot-body>
<p>Tem certeza de que deseja excluir permanentemente o item selecionado?</p>
<small>Esta ação não poderá ser desfeita.</small>
</div>
<div slot-footer>
<button class="btn-cancelar">Cancelar</button>
<button class="btn-perigo">Confirmar Exclusão</button>
</div>
</app-modal-dialog>
🎯 3. O Fim do ::ng-deep e Alternativas Modernas
Historicamente, desenvolvedores usavam ::ng-deep para forçar estilos em componentes filhos projetados. Hoje o ::ng-deep está oficialmente descontinuado (deprecated) e deve ser evitado.
As Duas Abordagens Recomendadas:
- CSS Custom Properties (Variáveis CSS):
O componente filho expõe variáveis CSS que penetram qualquer encapsulamento:
/* Componente Filho */ :host { background-color: var(--card-bg, #ffffff); color: var(--card-color, #333333); }O componente pai customiza diretamente:
/* Componente Pai */ app-card-filho { --card-bg: #1e1e2f; --card-color: #f1f1f1; } - Pseudo-classe
:host-context(): Permite aplicar estilos condicionais dependendo de classes presentes em ancestrais do componente (ex: modo noturno na tagbody)::host-context(.dark-mode) .modal-box { background-color: #121212; color: #ffffff; }
⚖️ 4. Matriz Comparativa: ViewEncapsulation
| Critério | Emulated | ShadowDom | None |
|---|---|---|---|
| Isolamento de Estilos | Escopo simulado via atributos HTML | Isolamento de hardware nativo do browser | Nenhum (escopo global no <head>) |
| Penetração de Estilos Globais | Estilos globais afetam seletores internos | Estilos globais NÃO entram | Estilos se misturam |
| Uso de Variáveis CSS | Suportado | Suportado | Suportado |
| Compatibilidade SSR | 100% Nativo e Leve | Requer suporte do navegador | Total |
⚠️ 5. Armadilhas Comuns e Anti-Patterns
- Estilizar Conteúdo Projetado dentro do CSS do Filho:
- Erro: O CSS do componente filho NÃO afeta os nós projetados via
<ng-content>. Quem estila os elementos projetados é o componente Pai, pois a origem dos nós é dele. - Correção: Se o filho precisa forçar estilos nos slots, use o pseudo-elemento
:host ::slotted(seletor)ou variáveis CSS.
- Erro: O CSS do componente filho NÃO afeta os nós projetados via
- Múltiplos
<ng-content>sem seletor (select):- Se você colocar dois
<ng-content />sem atributoselect, o Angular projetará todos os elementos no primeiro slot e deixará o segundo vazio.
- Se você colocar dois
🛡️ 6. Boas Práticas na Construção de Design Systems
- Crie Componentes Agósticos de Layout: Use
:host { display: block; }por padrão, pois custom elements sãoinlinepor especificação do HTML. - Fallbacks Nativos: Aproveite o recurso do Angular 18 para definir fallbacks dentro de
<ng-content>, reduzindo a necessidade de lógica condicional com@ifpara exibir estados vazios.
📋 7. Cheat-Sheet: Síntese de Seletores Especiais
:host /* Estiliza o próprio custom element (<app-card>) */
:host(.ativo) /* Aplica se o host tiver a classe 'ativo' */
:host-context(.dark) /* Aplica se algum elemento ancestral tiver '.dark' */
::slotted(button) /* Estiliza elementos projetados (no ShadowDom) */