Модуль 39

Архітектура реального Angular-проєкту

Ось урок, створений за твоїм майстер-промптом. Вмикай уяву, ми починаємо!


🎓 Тема: Архітектура реального Angular-проєкту

(або чому ми не будуємо хмарочоси з гілок і скотчу)


1. 🔥 Вступ: проблема та мотивація

Уявімо ситуацію. Ти вирішив побудувати будку для собаки. Тобі вистачить молотка, дощок і, можливо, одного вихідного. Ти тримаєш план у голові, і якщо приб’єш дошку трохи криво — не біда, Рекс не образиться.

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

Звісно, ні.

Так само і в програмуванні. Коли ти робиш "To-Do List" на одному компоненті — це собача будка. Все в одному файлі, все працює. Але реальний Angular-проєкт — це система, де є: * Сотні компонентів. * Десятки сервісів. * Команда з 5–10 розробників, які працюють одночасно.

Риторичне питання: Якщо ти покладеш шкарпетки, виделки та важливі документи в одну величезну коробку з написом "Різне", скільки часу ти витратиш, щоб знайти паспорт?

Без правильної архітектури: 1. Твій проєкт перетвориться на "спагеті-код", який страшно чіпати. 2. Виправлення багу в одному місці ламатиме щось в іншому. 3. Новий розробник витратить місяць, щоб просто зрозуміти, де що лежить.

Сьогодні ми навчимося розкладати все "по поличках", як це роблять в Google, Netflix чи великих банках.


2. 🧠 Теоретична база (без сухої академічності)

В Angular архітектура — це не про те, як написати код, а про те, куди його покласти і як частини спілкуються між собою.

Ось три кити, на яких тримається порядок. Запам’ятай їх (або запиши на стікері):

А. Розподіл відповідальності (Smart vs Dumb)

Уяви ресторан. * Dumb Component (Дурний/Презентаційний): Це офіціант. Він гарно виглядає, посміхається, приносить меню (Input) і передає замовлення на кухню (Output). Він не готує їжу і не знає, скільки продуктів на складі. * Smart Component (Розумний/Контейнер): Це шеф-кухар або менеджер. Він знає, що робити з даними, спілкується з сервісами (складом), обробляє логіку і каже "офіціантам", що показати клієнту.

Б. Структура папок (LIFT)

Є принцип LIFT: * Locate (Знайти код легко). * Identify (Зрозуміти, що це, по назві файлу). * Flat (Не роби вкладеність папок у 10 рівнів). * Try to be DRY (Don't Repeat Yourself).

В. Модульність (Feature, Core, Shared)

Навіть якщо ти використовуєш нові Standalone Components, логічний поділ залишається: 1. Core: Те, що існує в одному екземплярі на весь додаток (сервіс авторизації, інтерцептори HTTP). Це "фундамент". 2. Shared: Те, що використовується всюди (кнопки, інпути, пайпи форматування дат). Це "цеглинки". 3. Features (Фічі): Конкретні бізнес-розділи (сторінка "Профіль", сторінка "Каталог"). Це "кімнати".


3. 🧪 Приклади (від простого до реального)

Приклад 1: Хаос (Як не треба)

Ти відкриваєш папку src/app і бачиш:

app.component.ts
app.component.html
user-list.component.ts
user-details.component.ts
login.component.ts
button.component.ts
api.service.ts
auth.service.ts
header.component.ts
... (ще 50 файлів)

Питання до тебе: Як швидко ти знайдеш тут логіку редагування профілю користувача? Відповідь: Це пекло. Все змішано.


Приклад 2: Порядок (Як треба)

Давай застосуємо наші принципи.

src/app/
├── core/                  # Фундамент (один раз на весь апп)
│   ├── services/
│   │   └── auth.service.ts
│   └── guards/
├── shared/                # Спільні дурні компоненти
│   ├── components/
│   │   ├── btn-primary/
│   │   └── card/
│   └── pipes/
├── features/              # Бізнес-логіка (розділи сайту)
│   ├── home/
│   └── users/             # Модуль користувачів
│       ├── users-list/    # Smart компонент (сторінка)
│       └── user-card/     # Dumb компонент (картка одного юзера)
└── app.component.ts

Приклад 3: Smart vs Dumb у коді

Dumb (UserCard): Просто показує дані.

@Component({
  selector: 'app-user-card',
  standalone: true,
  template: `
    <div class="card">
      <h3>{{ user.name }}</h3>
      <button (click)="onDelete()">Видалити</button>
    </div>
  `
})
export class UserCardComponent {
  @Input() user!: User;             // Отримує дані зверху
  @Output() delete = new EventEmitter<number>(); // Кричить наверх: "Мене видаляють!"

  onDelete() {
    this.delete.emit(this.user.id);
  }
}

Smart (UsersList): Керує процесом.

@Component({
  selector: 'app-users-list',
  standalone: true,
  imports: [UserCardComponent], // Використовує Dumb компонент
  template: `
    <h1>Список користувачів</h1>
    @for (u of users; track u.id) {
      <app-user-card 
        [user]="u" 
        (delete)="handleDelete($event)">
      </app-user-card>
    }
  `
})
export class UsersListComponent {
  users: User[] = [];

  constructor(private userService: UserService) { // Спілкується з сервісом
    this.users = this.userService.getAll();
  }

  handleDelete(id: number) {
    this.userService.remove(id); // Викликає бізнес-логіку
    this.users = this.userService.getAll(); // Оновлює стан
  }
}

Чому так? Якщо ми захочемо змінити дизайн картки, ми змінимо лише UserCard. Логіка списку не зламається. Якщо зміниться API — ми правимо Service і UsersList. Розділяй і володарюй!


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

Час закачати рукави. Ось твої завдання:

Завдання 1. Ідентифікація У тебе є компонент ProductFilterComponent, який: 1. Малює чекбокси для фільтрації. 2. Сам робить HTTP-запит на сервер за списком категорій. 3. Фільтрує масив товарів всередині себе. Запитання: Чи правильно це? Якщо ні, як розділити цей компонент на Smart та Dumb?

Завдання 2. Рефакторинг структури Уяви, що тобі дали проєкт інтернет-магазину, де всі файли лежать в одній купі. Розподіли (на папері або в текстовому файлі) наступні файли по папках core, shared, features/cart, features/catalog: * header.component.ts (є на всіх сторінках) * api.service.ts (загальний для запитів) * product-item.component.ts (використовується в каталозі і в "схожих товарах") * shopping-cart-page.component.ts * checkout-button.component.ts (стилізована кнопка, юзається всюди) * catalog-page.component.ts

Завдання 3. Міні-кейс "Нова фіча" Менеджер каже: "Додай розділ 'Адмінка', де можна банити юзерів". Опиши, де ти створиш папку, які компоненти там будуть (назви хоча б один Smart і один Dumb) і чи будеш ти використовувати щось із shared?

Завдання 4. "А що, якщо..." А що, якщо UserCardComponent (з нашого прикладу) сам почне видаляти користувача через UserService, не повідомляючи UsersList? Яка проблема виникне, коли UsersList спробує відобразити кількість користувачів на екрані? (Підказка: синхронізація даних).


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

Як відрізнити новачка від сеньйора, глянувши на архітектуру?

  1. Новачок: Пише бізнес-логіку (виклики API, розрахунки цін) прямо в шаблоні HTML або всередині Dumb-компонентів.
    • Наслідок: Цей компонент неможливо використати в іншому місці.
  2. Новачок: Думає "Я покладу це в Shared, бо може колись знадобиться".
    • Наслідок: Папка Shared стає смітником.
    • Як думає профі: "Я покладу це у Feature-модуль. Якщо це знадобиться ще десь, тільки тоді я перенесу це в Shared". YAGNI (You Aren't Gonna Need It).
  3. Профі: Завжди думає про "Single Source of Truth" (Єдине джерело істини). Дані мають текти вниз (від Smart до Dumb), а події — вгору.

Порада: Перед тим як створити файл, зупинись на 5 секунд і запитай себе: "Хто буде батьком цього компонента? Чи зможу я використати його в іншому проєкті?".


6. 🧩 Підсумок

Сьогодні ми не написали багато коду, але ми зробили щось важливіше — ми навчилися думати системами.

Що ти тепер знаєш: * ✅ Чому все в одній купі — це зло. * ✅ Різницю між Core, Shared та Features. * ✅ Хто такі "Розумні" та "Дурні" компоненти (і чому бути "дурним" компонентом — це почесно). * ✅ Як організувати файли так, щоб колеги тебе не проклинали.

Тизер наступного уроку: Ми збудували гарний будинок (архітектуру), але в ньому ще немає електрики та води. Як дані "течуть" трубами додатку? Наступного разу ми поговоримо про RxJS та Observables — кровоносну систему Angular!


Ну що, готовий навести лад у своїх файлах? Це і є справжня інженерія. 😉