Fiecare componentă își aduce propriul CSS și, implicit, CSS-ul ăsta rămâne în componentă: o regulă .title dintr-o componentă nu atinge un .title din alta. Angular face asta rescriindu-ți selectorii când compilează componenta. Mecanismul se numește view encapsulation.
Dacă știi cum funcționează rescrierea, ai răspuns la întrebările pe care le pune oricine în prima lună: de ce CSS-ul părintelui nu ajunge la copil, de ce un stil apare brusc pe altă pagină și cum faci o componentă personalizabilă fără ::ng-deep.
Unde stau stilurile unei componente
Inline în styles sau într-un fișier, cu styleUrl. Oricum ar fi, aparțin componentei: Angular le adaugă în pagină când apare prima instanță și le scoate când e distrusă ultima. CSS-ul global — reset-uri, fonturi, design tokens — merge în styles.scss, care nu e încapsulat.
@Component({
selector: 'app-note',
template: `<p class="note"><ng-content /></p>`,
// stiluri inline…
styles: `
:host { display: block; }
.note { padding: 0.5rem 0.75rem; border-left: 3px solid var(--accent); }
`,
// …sau un fișier: styleUrl: './note.css'
})
export class Note {}Emulated: modul implicit
Angular dă fiecărei componente un id unic și îi marchează elementele cu două atribute: _nghost-… pe elementul gazdă și _ngcontent-… pe fiecare element din template. Apoi adaugă atributul pe fiecare parte a selectorilor tăi: .note devine .note[_ngcontent-ng-c123]. Regulile tale nu pot ieși din componentă — dar regulile din afară tot intră: CSS-ul global și componentele fără încapsulare se aplică normal.
/* ce scrii tu */
:host { display: block; }
.note { border-left: 3px solid var(--accent); }
.list .note:hover { background: var(--surface-2); }
/* ce primește browserul (Emulated) */
[_nghost-ng-c123] { display: block; }
.note[_ngcontent-ng-c123] { border-left: 3px solid var(--accent); }
.list[_ngcontent-ng-c123] .note[_ngcontent-ng-c123]:hover { background: var(--surface-2); }Cele patru moduri, live
ViewEncapsulation.None sare peste rescriere: stilurile devin globale imediat ce apare componenta. ShadowDom folosește Shadow DOM-ul adevărat al browserului, deci CSS-ul paginii nu intră. ExperimentalIsolatedShadowDom (experimental din v21) merge un pas mai departe, cum vezi mai jos.
import { ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-embed-widget',
// Emulated (implicit) · None · ShadowDom · ExperimentalIsolatedShadowDom
encapsulation: ViewEncapsulation.ShadowDom,
template: `…`,
styles: `…`,
})
export class EmbedWidget {}În altă parte pe pagină
Un paragraf cu clasa .enc-lab-note
Emulated (implicit)
None
Nemontată.
ShadowDom
ExperimentalIsolatedShadowDom
| Mod | Selectorii din browser | Unde stă stilul |
|---|
Regula componentei None ajunge la pagină și la componenta Emulated — și, surprinzător, și la cea ShadowDom. În modul ShadowDom, Angular copiază stilurile celorlalte componente din aplicație în fiecare shadow root. Selectorii Emulated nu se potrivesc acolo, dar cei globali da. ExperimentalIsolatedShadowDom pune în shadow root doar stilurile componentei. La SSR, ambele moduri shadow sunt randate ca Emulated.
Ce mod, când
| Mod | Stilurile tale ies? | Stilurile paginii intră? | Folosește-l pentru |
|---|---|---|---|
Emulated | nu | da | aproape orice |
None | da, devin globale | da | rar: markup pe care nu-l controlezi, o foaie de stil globală livrată ca o componentă |
ShadowDom | nu | nu — dar Angular copiază înăuntru stilurile celorlalte componente | widget-uri puse în paginile altora |
ExperimentalIsolatedShadowDom | nu | nu | izolare completă (experimental) |
:host, :host() și :host-context()
Elementul gazdă — chiar <app-card> — aparține template-ului părintelui, deci un selector obișnuit din componentă nu ajunge la el. :host ajunge. Gazda pornește cu display: inline, așa că cele mai multe componente au nevoie de :host { display: block }. :host(.active) se aplică atunci când gazda are o clasă; :host-context(.dark), când o are orice strămoș.
/* elementul gazdă: chiar <app-card> */
:host {
display: block; /* gazdele sunt inline implicit */
border: 1px solid var(--border);
}
/* o variantă oferită de componentă: <app-card class="compact"> */
:host(.compact) {
padding: 0.5rem;
}
/* când un strămoș are clasa (emulat în modul Emulated) */
:host-context(.print) {
border-color: black;
}Preferă :host() pentru variantele pe care le oferă o componentă. Angular emulează :host-context() în modul Emulated, dar într-un shadow root adevărat doar Chromium îl suportă. Pentru teme, variabilele CSS merg peste tot.
Cum stilizezi un copil din părinte
Întrebarea pe care o pune toată lumea: „CSS-ul meu nu merge pe componenta copil!” Selectorii părintelui primesc atributul părintelui, iar elementele din copil îl poartă pe al copilului — deci nu se potrivesc niciodată. Iată cum încearcă oamenii, compilat live.
- A ajuns la buton
- nu
- Regulă globală
- nu
Fiecare parte a selectorului a primit atributul părintelui. Butonul din copil poartă atributul copilului, deci nu se potrivește nimic.
.attempt-plain app-buy-button button {
background: var(--bad);
}Browserul primește—Ultimele două rezistă la refactorizări: copilul publică ce se poate schimba, iar părintele folosește doar atât. ::ng-deep e deprecated, dar nu are înlocuitor, așa că Angular îl păstrează pentru cazurile în care nu tu deții copilul — stilizarea unei biblioteci externe, de exemplu. Chiar și atunci, începe-l mereu cu :host. Fii atent și la specificitate: în modul Emulated, .buy[_ngcontent-…] din copil contează ca două clase, deci o regulă din afară are nevoie de cel puțin atât ca să câștige.
/* ✗ global: activ pe fiecare pagină cât timp există componenta */
::ng-deep .mat-mdc-button { text-transform: uppercase; }
/* ✓ dacă chiar trebuie: doar în interiorul acestei componente */
:host ::ng-deep .mat-mdc-button { text-transform: uppercase; }Teme cu variabile CSS
Declară în copil variabile cu valori de rezervă — var(--buy-bg, var(--surface-2)) — și setează-le oriunde mai sus: în styles.scss pentru toată aplicația, pe un container pentru o secțiune sau cu [style.--buy-bg] pentru o singură instanță. Modul întunecat devine atunci doar alt set de valori.
@Component({
selector: 'app-buy-button',
template: `<button class="buy"><ng-content /></button>`,
styles: `
/* API-ul public de stil: variabile cu valori de rezervă… */
.buy {
background: var(--buy-bg, var(--surface-2));
color: var(--buy-fg, var(--text));
border-radius: var(--buy-radius, 8px);
}
/* …și variantele pe care le suportă copilul */
:host(.danger) .buy {
--buy-bg: var(--bad-soft);
--buy-fg: var(--bad);
}
`,
})
export class BuyButton {}Ce intră în styles.scss
Reset-uri, fonturi, design tokens (culori, spațieri și raze ca variabile) și stiluri pentru HTML care nu e randat de componentele tale — innerHTML dintr-un CMS, de exemplu. Tot restul aparține componentei care îl randează.
Clasele și stilurile legate dinamic sunt la Class & style bindings; cum se comportă Shadow DOM într-un Web Component, la Web Components cu Angular Elements.
Capcane frecvente
- O componentă care ignoră width și margin. Gazda e implicit
display: inline. Adaugă:host { display: block }. ViewEncapsulation.Noneca să „repari” un selector. Stilurile devin globale și rămân așa: lovesc fiecare element cu clasa respectivă cât timp există componenta.::ng-deepfără:host. Aceeași problemă, câte o regulă pe rând.- Stilizezi markup-ul intern al copilului. Merge cu
::ng-deeppână când copilul își schimbă template-ul. Cere-i copilului o variabilă sau o variantă. - Te bazezi pe
ShadowDompentru izolare. Angular copiază în shadow root stilurile celorlalte componente. Dacă ai nevoie de izolare reală, uită-te laExperimentalIsolatedShadowDom.
Fișă rapidă
styles / styleUrl CSS-ul componentei; adăugat cu prima instanță, scos odată cu ultima
Emulated (default) selectorii primesc [_ngcontent-…]; nimic nu iese, CSS-ul paginii tot intră
None fără rescriere: stilurile sunt globale cât timp există componenta
ShadowDom shadow root adevărat; Angular tot copiază înăuntru stilurile celorlalte componente
Isolated (exp. v21) ExperimentalIsolatedShadowDom: doar stilurile componentei
:host elementul gazdă; inline implicit → :host { display: block }
:host(.x) o variantă a gazdei: <app-card class="x">
:host-context(.x) un strămoș are .x; evită-l într-un shadow root adevărat
::ng-deep oprește delimitarea; deprecated; mereu :host ::ng-deep, doar pentru cod care nu e al tău
CSS variables se moștenesc peste orice graniță: API-ul public de stil al unei componente
styles.scss reset-uri, fonturi, tokens, markup din CMS/innerHTML — nu stiluri de componente