Модуль 4 · урок 13 з 13
Практика модуля
Найбільша практика в плані. Тут застосунок нарешті отримує дані — і саме цей модуль лід дивитиметься найуважніше, бо в ньому видно, чи розумієш ти асинхронність, чи просто навчився складати компоненти.
КРОК 1Контракт
Випиши ендпоїнти у файл docs/api-contract.md у стилі Swagger: метод, шлях,
параметри, форма відповіді, коди помилок. Опиши DTO окремо від доменної моделі
й напиши мапер між ними — як у модулі 2.
Коміт: docs: describe api contract.
КРОК 2Інтерфейс і токен
Опиши інтерфейс TaskApi з усіма методами й типізованими сигнатурами.
Заведи InjectionToken і зареєструй мок-реалізацію в
app.config.ts. Жодного any.
КРОК 3Генератор і мок
Згенеруй 800–1000 сутностей. Реалізуй мок: фільтрація, сортування, пагінація — усе на боці сервісу, ніби це робить бекенд. Обовʼязково: затримка 300–800 мс, кожен N-й виклик падає з 500, створення з дублікатом коду повертає 409. Стан живе між викликами.
КРОК 4Стор
Зроби TaskStore за патерном з уроку 9: приватні BehaviorSubject
для даних, фільтрів, стану завантаження й помилки; публічні Observable тільки
для читання; похідний visible$ через combineLatest.
Дії — методи, жодного .next() ззовні.
КРОК 5Реактивний екран
Зв'яжи фільтри й пошук: startWith → debounceTime(300) →
distinctUntilChanged → перерахунок. Компонент — тонкий: жодного
subscribe, усе через async pipe або toSignal.
Перевір, що швидкий набір у пошуку не створює зайвих запитів.
КРОК 6Стани й помилки
Три стани на екрані: завантаження, помилка з кнопкою «спробувати ще», порожній результат.
catchError усередині switchMap, finalize для
індикатора. Повідомлення людською мовою, не текст з бекенду. Перевір, що після
штучного збою екран продовжує працювати.
КРОК 7Дія зі змінами
Зроби хоча б одну дію, що змінює дані — наприклад, зміну статусу з рядка списку.
Оптимістичне оновлення з відкатом при помилці. Оператор — concatMap
або exhaustMap, не switchMap.
КРОК 8Перевірка на витоки
Прогони по застосунку пошуком .subscribe(: кожне входження має бути
або в сторі з takeUntilDestroyed, або разовою дією. У компонентах —
нуль. Потім Heap snapshot: зайди на екран і назад десять разів, порівняй кількість
екземплярів компонента.
- Дані мокаються строго в сервісі заописаним контрактом; є затримка й керовані збої.
- Нуль ручних
.subscribe()у компонентах. - Стан живить UI через
BehaviorSubjectіcombineLatest. - Пошук із
debounceTimeіswitchMap; зайвих запитів немає. - Помилка не ламає потік: після збою екран продовжує працювати.
- Є всі стани: завантаження, помилка, порожньо, дані.
- Заміна мока на HTTP-реалізацію потребує зміни одного рядка.
Що сказати ліду на демо
«Дані мокаються на рівні сервісу за контрактом, який я виписав окремо — компоненти й стор
залежать від інтерфейсу, тож підміна на справжній API це один рядок у конфізі.
Стан у сторі: приватні BehaviorSubject, назовні тільки читання, похідний список
рахується через combineLatest — окремої копії відфільтрованих даних немає.
Пошук: debounceTime плюс distinctUntilChanged плюс
switchMap, тож гонок відповідей не буває. catchError стоїть усередині
switchMap, щоб помилка не вбивала зовнішній потік — після збою пошук працює далі.
У компонентах жодного subscribe: усе через async pipe.»
Кроки 1–3 — один-два вечори, вони механічні. Крок 4–5 — найважчі, закладай три-чотири вечори й не засмучуйся, якщо перша версія стору вийде кривою: її нормально переписати вдруге. Кроки 6–8 — ще два. Разом це найдовший модуль плану, і це нормально: він же й найцінніший для співбесід.
Далі — модуль 5. Грид: сортування, пагінація, фільтри й вибір рядків. Після цього модуля дані вже будуть, і таблиця стане просто ще одним споживачем твого стору — саме тому порядок модулів такий.