Angular IDPStrong Junior
🔥 0

Модуль 3 · урок 6 з 11

ViewEncapsulation

Стилі компонента за замовчуванням не витікають назовні й не приймають чужого впливу. Це зручно рівно доти, доки не треба стилізувати щось усередині чужого компонента — і тоді починається війна з ::ng-deep. Розберемось, як воно влаштоване й що робити натомість.

Три режими

РежимЯк працюєКоли
Emulated (за замовчуванням)Angular додає елементам унікальні атрибути й дописує їх у селекториМайже завжди
NoneСтилі стають глобальними, як звичайний CSS-файлТема, скидання, стилі для чужих компонентів
ShadowDomСправжня ізоляція браузером через Shadow DOMВіджети для вбудовування в чужі сторінки

Як влаштована Emulated

/* ти пишеш */
.card__title { font-weight: 700; }
/* Angular генерує */
.card__title[_ngcontent-abc-1] { font-weight: 700; }
<h3 class="card__title" _ngcontent-abc-1>…</h3>

Тобто ізоляції на рівні браузера немає — це домовленість через атрибути. Наслідки, які варто знати:

  • Специфічність зростає на одиницю (атрибут важить як клас) — тому чужі глобальні стилі перебити стало трохи легше, а свої компонентні ззовні — трохи важче.
  • Атрибут ставиться лише на елементи шаблону цього компонента. Розмітка, яка прийшла через <ng-content> або з дитячого компонента, його не має — тому й не стилізується.
  • Динамічно створений через innerHTML вміст теж лишається без атрибута.

:host і :host-context

:host {
  display: block;              /* хост за замовчуванням inline — класична пастка */
  border: 1px solid var(--rule);
}

:host(.is-selected) {          /* коли на самому елементі є клас */
  border-color: var(--accent);
}

:host([data-size="sm"]) { padding: 8px; }

:host-context(.dense) .card { padding: 4px; }   /* коли клас є в будь-якого предка */

:host — це єдиний спосіб стилізувати сам елемент компонента: у шаблоні його немає, бо шаблон описує лише вміст.

::ng-deep і чому його не варто

/* ❌ пробиває ізоляцію: стиль тече в усі вкладені компоненти */
::ng-deep .mat-mdc-form-field { padding: 0; }

/* трохи менше зло: обмежити своїм хостом */
:host ::ng-deep .mat-mdc-form-field { padding: 0; }

Проблем із ::ng-deep дві. Перша — він офіційно застарілий: колись його приберуть. Друга гірша: без :host перед ним стиль стає фактично глобальним і зачіпає компоненти, про які ти не думав. Знайти таке потім важко — селектор лежить у файлі одного компонента, а ламає інший.

Що робити натомість

/* 1. CSS-змінні проходять крізь будь-яку ізоляцію — це головний інструмент */
:host {
  --field-padding: 4px 8px;
}

/* 2. Глобальні стилі для чужої бібліотеки — у styles.scss, свідомо й в одному місці */

/* 3. Для власних компонентів — нормальний API: input, клас-модифікатор, токен */
Чому саме змінні

Ізоляція обмежує селектори, але не каскад значень. Кастомна властивість, оголошена на :host або на :root, доступна всередині будь-якого нащадка — навіть у Shadow DOM. Тому дизайн-токени з модуля 1 працюють скрізь і не потребують жодних ::ng-deep. Наступний урок саме про це.

ViewEncapsulation.None: коли це доречно

@Component({
  selector: 'app-root',
  encapsulation: ViewEncapsulation.None,   // свідомо, з коментарем чому
})

Виправданих випадків небагато: кореневий компонент, який задає тему; окремий компонент-«міст» для стилізації сторонньої бібліотеки. У всіх інших місцях None — це той самий ::ng-deep, тільки на весь файл.

Вимога з таблиці

Лід просить явну ViewEncapsulation. Тобто справа не в тому, щоб щось вимкнути, а в тому, щоб у метаданих компонента було видно намір. Найчастіше це ViewEncapsulation.Emulated написана явно — і одне-два свідомі виключення з коментарем.

≈ 40 хв · +25 XP за урок