Ось урок, створений за твоїм майстер-промптом. Вмикай уяву, ми починаємо!
🎓 Тема: Архітектура реального 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. 💡 Мислення як у розробника
Як відрізнити новачка від сеньйора, глянувши на архітектуру?
- Новачок: Пише бізнес-логіку (виклики API, розрахунки цін) прямо в шаблоні HTML або всередині Dumb-компонентів.
- Наслідок: Цей компонент неможливо використати в іншому місці.
- Новачок: Думає "Я покладу це в Shared, бо може колись знадобиться".
- Наслідок: Папка
Sharedстає смітником. - Як думає профі: "Я покладу це у Feature-модуль. Якщо це знадобиться ще десь, тільки тоді я перенесу це в Shared". YAGNI (You Aren't Gonna Need It).
- Наслідок: Папка
- Профі: Завжди думає про "Single Source of Truth" (Єдине джерело істини). Дані мають текти вниз (від Smart до Dumb), а події — вгору.
Порада: Перед тим як створити файл, зупинись на 5 секунд і запитай себе: "Хто буде батьком цього компонента? Чи зможу я використати його в іншому проєкті?".
6. 🧩 Підсумок
Сьогодні ми не написали багато коду, але ми зробили щось важливіше — ми навчилися думати системами.
Що ти тепер знаєш: * ✅ Чому все в одній купі — це зло. * ✅ Різницю між Core, Shared та Features. * ✅ Хто такі "Розумні" та "Дурні" компоненти (і чому бути "дурним" компонентом — це почесно). * ✅ Як організувати файли так, щоб колеги тебе не проклинали.
Тизер наступного уроку: Ми збудували гарний будинок (архітектуру), але в ньому ще немає електрики та води. Як дані "течуть" трубами додатку? Наступного разу ми поговоримо про RxJS та Observables — кровоносну систему Angular!
Ну що, готовий навести лад у своїх файлах? Це і є справжня інженерія. 😉