Модуль 14

Стилі в Angular: CSS, SCSS та View Encapsulation

Ось твій урок у стилі CS50! 🚀


🎨 Тема: Стилі в Angular (CSS, SCSS та View Encapsulation)

Привіт, друзі! Мене звати [Твоє Ім'я], і це... CS50 Angular Edition.

Сьогодні ми поговоримо про те, що робить наші додатки не лише функціональними, а й красивими. Але, що важливіше, ми розберемося, як зробити так, щоб ця краса не перетворила ваш код на пекло. Ми говоримо про стилі та інкапсуляцію.


1. 🔥 Вступ: Чому CSS — це небезпечно?

Уявіть собі ситуацію. Ви будуєте величезний хмарочос (ваш веб-додаток). У вас є сотні кімнат (компонентів).

Ви заходите в одну кімнату — скажімо, "Кухню" (компонент KitchenComponent) — і вирішуєте пофарбувати стіни в червоний колір. Ви пишете правило: стіна { колір: червоний; }.

Виходите в коридор... і бачите жах. УСІ стіни в усьому хмарочосі — у спальні, у ванній, у холі — стали червоними! 😱

Питання до вас: Чому так сталося? Тому що стандартний CSS (Cascading Style Sheets) — він глобальний. За замовчуванням, клас .title діє на всю сторінку.

У великих проектах це катастрофа. Ви змінюєте кнопку в "Кошику", а ламається верстка в "Профілі користувача".

Як цього уникнути? Нам потрібна ізоляція. Нам потрібно, щоб те, що відбувається у Вегасі... тобто в компоненті, залишалося в компоненті.

В Angular це називається View Encapsulation (Інкапсуляція вигляду). І це працює магічно. Давайте зазирнемо під капот!


2. 🧠 Теоретична база: Магія під капотом

В Angular кожен компонент — це маленький самодостатній всесвіт. Він має три файли: 1. .ts (Логіка — мозок) 2. .html (Шаблон — скелет) 3. .scss/.css (Стилі — шкіра)

📌 Що треба зрозуміти залізно:

Angular бере ваші стилі й "обманює" браузер, щоб вони застосовувалися тільки до поточного компонента.

Є три режими цієї "магії" (стратегії інкапсуляції):

  1. Emulated (За замовчуванням і найважливіше!) 🏆 Angular не використовує справжній Shadow DOM браузера (це складна технологія). Натомість він діє хитріше.

    • Він бере ваш HTML і додає до кожного елемента унікальний атрибут, наприклад _ngcontent-c12.
    • Він бере ваш CSS і переписує його: h1 { color: red; } ➡️ h1[_ngcontent-c12] { color: red; }
    • Результат: Ваші стилі діють тільки на елементи з цим унікальним атрибутом. Сусідній компонент матиме атрибут _ngcontent-c13, і ваші стилі його не чіпатимуть.
  2. None ☠️ Ніякої ізоляції. Ваші стилі стають глобальними (додаються в <head>). Якщо ви напишете тут h1 { color: red }, всі заголовки на сайті почервоніють. Використовуйте це дуже обережно (а краще — ніколи).

  3. ShadowDom 🛡️ Використовує нативний API браузера для ізоляції. Це "справжня" ізоляція, але старі браузери можуть її не підтримувати, і глобальні стилі (наприклад, Bootstrap) туди не проникнуть взагалі.

🎨 CSS чи SCSS?

Angular "з коробки" підтримує SCSS (Sass). Це той самий CSS, але на стероїдах. * Вкладеність (Nesting): Можна писати стилі один в одному, як структуру HTML. * Змінні: $primary-color: blue;. * У підсумку Angular все одно перетворить (скомпілює) SCSS у звичайний CSS, який розуміє браузер.


3. 🧪 Приклади: Від простого до "Ой, зламалося"

Приклад 1: Базова ізоляція

У нас є UserComponent.

user.component.scss:

h1 {
  color: blue;
  border-bottom: 2px solid grey;
}

user.component.html:

<h1>Привіт, я юзер!</h1>

Що ви очікуєте побачити у браузері, якщо натиснете "Inspect Element"? Ви не побачите просто <h1>.

Реальність:

<h1 _ngcontent-ng-c42>Привіт, я юзер!</h1>

А в стилях браузера буде:

h1[_ngcontent-ng-c42] {
  color: blue;
  border-bottom: 2px solid grey;
}

Саме цей дивний атрибут _ngcontent... гарантує, що h1 в AdminComponent не стане синім.


Приклад 2: Використання SCSS та :host

Часто нам треба стилізувати сам компонент (його тег, наприклад <app-user>), а не те, що всередині нього. Для цього є псевдоклас :host.

user.component.scss:

:host {
  display: block;
  border: 1px solid black;
  padding: 20px;

  // SCSS Nesting: стилізуємо h1, який знаходиться всередині host
  h1 {
    color: green;
  }
}

Це означає: "Я, компонент app-user, матиму рамку. А мої заголовки будуть зеленими".


Приклад 3: Проблема "Батьки і Діти" (і ::ng-deep)

Уявіть, що у вас є CardComponent, і ви використовуєте його всередині PageComponent.

page.component.html:

<div class="page">
  <app-card></app-card> <!-- Це дитина -->
</div>

Ви хочете з батька (Page) змінити колір тексту в дитині (Card).

page.component.scss:

app-card {
  .card-title { // Клас знаходиться всередині шаблону app-card
    color: purple;
  }
}

Питання: Чи спрацює це? Відповідь: НІ!

Чому? Тому що Angular додасть атрибут батька (_ngcontent-page) до селектора. Але HTML дитини має атрибут дитини (_ngcontent-card). Вони не збігаються! Angular захищає дитину від впливу батька.

Рішення (але обережне!): ::ng-deep

app-card {
  ::ng-deep .card-title {
    color: purple;
  }
}

::ng-deep каже Angular: "Ігноруй інкапсуляцію для цього селектора!". Стиль проникне всередину дочірнього компонента.

⚠️ Увага: Використовуйте ::ng-deep ТІЛЬКИ разом із :host або класом батька, інакше цей стиль стане глобальним для всього проекту!


4. 🛠 Практична частина

Час забруднити руки кодом! Відкрийте свій IDE.

Завдання 1: "Детектив" 🕵️‍♂️

  1. Створіть два компоненти: HeaderComponent і FooterComponent.
  2. В обох додайте тег <p>Текст</p>.
  3. У header.component.scss задайте p { color: red; }.
  4. Запустіть додаток (ng serve).
  5. Місія: Відкрийте DevTools (F12), знайдіть тег <p> у Header і Footer. Подивіться на атрибути _ngcontent. Переконайтеся, що вони різні, і що Footer не червоний.

Завдання 2: "SCSS Майстер" 🎨

У app.component.scss використайте змінну та вкладеність:

$main-color: #3f51b5;

.container {
  background-color: #eee;

  button {
    background-color: $main-color;
    color: white;

    &:hover {
      background-color: darken($main-color, 10%);
    }
  }
}

Перевірте, чи працює ховер ефект.

Завдання 3: "Порушник правил" 😈

  1. У HeaderComponent (.ts файл) додайте у декоратор: encapsulation: ViewEncapsulation.None.
  2. Залиште стилі p { color: red; }.
  3. Подивіться на сторінку. Чи став текст у FooterComponent червоним? (Спойлер: Так).
  4. Висновок: Поверніть як було, негайно!

Завдання 4: "Виклик" 🧠

Спробуйте стилізувати компонент так, щоб він мав рамку, тільки якщо у нього є клас active. Підказка: Вам знадобиться конструкція :host(.active).


5. 💡 Мислення як у розробника

Як думають сеньйори, коли пишуть стилі в Angular?

  1. Компонент — це фортеця. Я пишу стилі так, ніби інших компонентів не існує. Я не боюся використовувати прості назви класів типу .title чи .card, бо знаю, що Angular їх ізолює. Мені не треба писати .user-profile-page-header-title.
  2. Глобальні стилі — для глобальних речей. Шрифти, кольори бренду, скидання відступів (reset.css) я кладу в styles.scss (глобальний файл). Все інше — в компоненти.
  3. Уникайте ::ng-deep як вогню. Якщо вам доводиться його використовувати, це сигнал: "Можливо, архітектура мого компонента неправильна?". Краще передати налаштування через @Input() (наприклад, color="primary"), ніж "ламати" стилі ззовні.
  4. Не пишіть логіку в CSS. Якщо кнопка має зникнути, використовуйте *ngIf, а не display: none, якщо це можливо з точки зору безпеки та логіки.

6. 🧩 Підсумок

Сьогодні ми зрозуміли, що: * CSS в Angular локальний завдяки Emulated View Encapsulation. * _ngcontent-xyz — це ті "мітки", які Angular розставляє, щоб розрізняти компоненти. * :host дозволяє стилізувати сам контейнер компонента. * SCSS допомагає писати чистіший код завдяки вкладеності.

Тепер ви можете фарбувати стіни у своїй "кімнаті", не боячись, що сусіди викличуть поліцію!

Що далі? Тепер, коли наші компоненти виглядають стильно, час змусити їх змінюватись динамічно. На наступному уроці ми поговоримо про Структурні та Атрибутні Директиви (*ngIf, *ngFor, [ngClass]). Це буде як навчити наш будинок перебудовувати кімнати прямо на льоту!

А поки що — щасти вам з кодом! 👋