Модуль 11

Директиви: вбудовані та користувацькі

Ось готовий урок, створений у стилі CS50, де ми розберемо тему директив на прикладі Angular (оскільки саме в цьому фреймворку концепція "Директив" реалізована найбільш академічно та наочно).


🏛 CS50: Директиви — Магія управління DOM-деревом

Привіт, друзі! Мене звати [Твоє Ім'я], і це CS50... тобто, наш урок з архітектури веб-додатків!

Сьогодні ми поговоримо про річ, яка перетворює статичний HTML на щось живе, що дихає і реагує на ваші дії. Ми поговоримо про Директиви.


1. 🔥 Вступ: Чому без цього ніяк?

Уявіть собі ситуацію. Ви будуєте розумний будинок. У вас є звичайна лампочка. Щоб вона загорілася, коли ви заходите в кімнату, вам потрібно підійти до вимикача і фізично натиснути його.

У світі JavaScript це виглядало б так: 1. Знайти елемент у DOM (document.getElementById). 2. Перевірити умову. 3. Змінити йому стиль (element.style.display = 'block').

А тепер уявіть, що у вас 100 лампочок. І 50 кімнат. Ви будете бігати й клацати кожним вимикачем вручну? Ні.

Ви хочете наклеїти на лампочку стікер із написом: "Вмикайся, якщо хтось є в кімнаті" — і нехай вона сама розбирається, як це зробити.

Питання до вас: Чи не було б круто, якби ми могли навчити HTML "думати"? Щоб ми могли написати в коді <div *if="userIsAdmin">, і блок з'являвся б тільки для адміна, без жодного рядка document.querySelector?

Саме це і роблять Директиви. Це інструкції для браузера: "Гей, коли бачиш цей елемент, зроби з ним дещо особливе".


2. 🧠 Теоретична база (Що "під капотом"?)

Давайте спростимо. Якщо Компонент (Component) — це іменник (це "Віджет погоди", це "Картка товару"), то Директива — це дієслово або прикметник.

Директива не має власного шаблону (HTML). Вона "паразитує" на існуючому елементі та змінює його поведінку.

Є два головні типи директив, які вам треба розрізняти (якщо ви це зрозумієте, ви зрозуміли 80% теми):

🅰️ Структурні директиви (Structural Directives)

  • Аналогія: Будівельник або фокусник.
  • Що роблять: Вони фізично додають або видаляють елементи з DOM-дерева.
  • Як впізнати: Зазвичай починаються із зірочки * (наприклад, *ngIf, *ngFor).
  • Важливо: Якщо умова false, елемент не просто ховається (CSS hidden) — він зникає з пам'яті. Його не існує.

🅱️ Атрибутні директиви (Attribute Directives)

  • Аналогія: Маляр або стиліст.
  • Що роблять: Вони змінюють зовнішній вигляд або поведінку вже існуючого елемента.
  • Як впізнати: Виглядають як звичайні атрибути HTML, часто у квадратних дужках [] (наприклад, [ngStyle], [ngClass]).
  • Важливо: Елемент залишається на місці, просто стає зеленим, починає миготіти або слухати кліки.

3. 🧪 Приклади (Show me the code!)

Давайте подивимось, як це виглядає в коді (на прикладі Angular, але логіка схожа у Vue/React).

Рівень 1: Вбудовані директиви (Те, що вже є в коробці)

У нас є змінна isLoggedIn = false.

<!-- Структурна директива *ngIf -->
<div *ngIf="isLoggedIn">
  Ласкаво просимо, Користувачу!
</div>

<div *ngIf="!isLoggedIn">
  Будь ласка, увійдіть.
</div>

👉 Що тут відбувається? Angular дивиться на isLoggedIn. Якщо це false, перший div навіть не рендериться. Його немає в HTML коді сторінки.

Рівень 2: Списки (Loop)

У нас є масив: users = ['Оля', 'Петро', 'Марія'].

<!-- Структурна директива *ngFor -->
<ul>
  <li *ngFor="let user of users">
    {{ user }}
  </li>
</ul>

👉 Очікування: Що ми побачимо на екрані? Правильно: Три пункти списку. Якщо ми додамо "Івана" в масив, Angular автоматично створить четвертий <li>.

Рівень 3: Користувацька директива (Створюємо свою магію)

А тепер уявіть: ви хочете, щоб при наведенні мишки на текст він ставав жовтим. Вбудованої директиви *MakeItYellow немає.

Ми створюємо свою директиву appHighlight.

// highlight.directive.ts
@Directive({
  selector: '[appHighlight]' // Це ім'я, яке ми шукаємо в HTML
})
export class HighlightDirective {

  constructor(private el: ElementRef) {}

  // Слухаємо подію 'mouseenter' (наведення миші)
  @HostListener('mouseenter') onMouseEnter() {
    this.highlight('yellow');
  }

  // Слухаємо, коли миша йде геть
  @HostListener('mouseleave') onMouseLeave() {
    this.highlight('');
  }

  private highlight(color: string) {
    // Прямий доступ до DOM (безпечно, бо через обгортку)
    this.el.nativeElement.style.backgroundColor = color;
  }
}

Використання в HTML:

<p appHighlight>Наведи на мене мишку!</p>

👉 Як це працює? 1. Angular бачить атрибут appHighlight. 2. Створює екземпляр класу HighlightDirective. 3. Передає йому посилання на цей <p> (через ElementRef). 4. Директива починає "слухати" події миші на цьому параграфі.


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

Час забруднити руки кодом! Виконайте ці завдання (подумки або у редакторі):

  1. 🔹 Рефакторинг: У вас є 5 однакових карток товарів, прописаних вручну в HTML. Як використати *ngFor, щоб скоротити код до 1 блоку?
  2. 🔹 Логічна пастка: Використайте *ngIf, щоб приховати кнопку "Купити", якщо товару немає на складі (quantity === 0).
  3. 🔹 Своя директива (Level Up): Напишіть (псевдокодом) директиву appPasswordToggle.
    • Задача: При кліку на іконку ока, вона має знаходити сусідній input і змінювати його тип з password на text і навпаки.
  4. 🔹 Кейс "А що, якщо...": Ви застосували директиву *ngIf і директиву *ngFor на одному й тому ж елементі.
    • Питання: Чому це погана ідея і викличе помилку? (Підказка: як браузер зрозуміє, що робити першим — створювати список чи перевіряти умову показу?)
  5. 🔹 Бізнес-задача: Менеджер просить: "Я хочу, щоб усі ціни, вищі за 1000 грн, автоматично підсвічувались червоним". Яку директиву ви створите — структурну чи атрибутну?

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

Послухайте уважно, це відрізнить вас від новачків.

Новачок думає: "Мені треба змінити колір кнопки. Я піду в компонент, напишу функцію changeColor(), знайду кнопку через ID..."

Сеньйор думає: "Зміна кольору при наведенні — це поведінка, а не бізнес-логіка компонента. Цю логіку я, можливо, захочу використати ще на 20 інших кнопках. Я винесу це в окрему Директиву".

Типові помилки: 1. Прямий доступ до DOM в компоненті. Ніколи не пишіть document.getElementById всередині Angular/React компонента, якщо це можна зробити директивою. Це ламає абстракцію. 2. Плутанина типів. Спроба використати структурну директиву як атрибутну (очікувати, що *ngIf просто сховає елемент через CSS, а не знищить його). Це може призвести до втрати даних, введених користувачем у форму, яка "зникла".


6. 🧩 Підсумок

Отже, що ми сьогодні вивчили:

  1. Директиви — це спосіб навчити HTML новим трюкам.
  2. Структурні (*) — змінюють структуру DOM (додають/видаляють). Як будівельники.
  3. Атрибутні ([]) — змінюють вигляд/поведінку. Як декоратори.
  4. Користувацькі — коли вбудованих не вистачає, ми пишемо свої, щоб дотримуватися принципу DRY (Don't Repeat Yourself).

Що ви тепер вмієте? Ви можете не просто виводити дані, а керувати тим, як і коли вони відображаються, причому робити це елегантно, використовуючи декларативний стиль.

🔜 Далі в курсі: Ми навчилися керувати елементами. Але як передавати дані між ними? На наступному уроці ми розберемо Input/Output та Сервіси — кровоносну систему нашого додатку.

А поки що... це був CS50! 🖐🎤 (кидає мікрофон)