Модуль 5 · урок 4 з 10
Клієнт чи сервер
Фільтрувати, сортувати й гортати можна на клієнті або на сервері. У таблиці ліда прямо написано «client-side pagination», тож у практиці робимо клієнтську — але розуміти межу треба, бо на роботі майже завжди буде навпаки.
Дві моделі
Де межа
| Клієнтська | Серверна | |
|---|---|---|
| Скільки рядків | До кількох тисяч | Від десятків тисяч |
| Реакція на фільтр | Миттєва | Затримка мережі |
| Перший запит | Важкий | Легкий |
| Складність фронтенду | Уся логіка в тебе | Мінімальна |
| Складність бекенду | Мінімальна | Уся логіка там |
| Актуальність даних | Застарівають після завантаження | Завжди свіжі |
Практичний орієнтир: якщо весь набір даних важить менше умовного мегабайта й не змінюється щосекунди — бери клієнтську. Вона дає різко кращий досвід: фільтри реагують без затримки, сортування миттєве, і не треба лікувати гонки запитів.
Клієнтська: одна чиста функція
export function applyState<T>(rows: readonly T[], state: TableState): Page<T> {
let out = rows;
// 1. пошук
if (state.query) {
const q = normalize(state.query);
out = out.filter(r => matches(r, q));
}
// 2. фільтри по колонках
for (const [key, value] of Object.entries(state.filters)) {
if (value) out = out.filter(r => String(r[key as keyof T]) === value);
}
// 3. сортування
if (state.sort.length) {
out = [...out].sort(compareBy(state.sort)); // копія: sort мутує
}
// 4. зріз сторінки — ОБОВʼЯЗКОВО останнім
const total = out.length;
const start = (state.page - 1) * state.size;
return { items: out.slice(start, start + state.size), total };
}
Фільтрувати → сортувати → різати. Якщо порізати раніше, відсортуєш лише поточну сторінку,
а total покаже кількість усіх рядків, а не знайдених. Це два різні баги,
які часто зустрічаються разом.
Головна перевага такого підходу — applyState це чиста функція.
Її можна протестувати без Angular взагалі, і в модулі 8 це буде один із найпростіших тестів.
Серверна: та сама функція, тільки в моці
// мок із модуля 4 просто виконує applyState у себе всередині
list(state: TableState): Observable<Page<Task>> {
return this.simulate(() => applyState(this.tasks, state));
}
Ось чому мок-шар із контрактом був вартий зусиль: перехід на серверну модель — це перенесення
однієї функції з фронтенду на бекенд. Компоненти й стор не змінюються взагалі, змінюється
тільки те, хто виконує applyState.
Гібрид, який часто зустрічається
// пагінація й фільтри на сервері, сортування поточної сторінки на клієнті
// ⚠️ так робити не варто: користувач сортує лише 25 видимих рядків,
// а думає, що сортує весь набір
Це класична пастка напівмір. Якщо пагінація серверна, то сортування й фільтрація теж мають бути серверними — інакше інтерфейс бреше. Змішувати можна лише свідомо й із поясненням користувачу.