Фундамент Ф1 · урок 12 з 12
Практика: список задач на чистому JS
Час зібрати все разом. Ти напишеш маленький застосунок — список задач — на чистому JavaScript: додавання, позначка «виконано», фільтр і лічильник. Тут працюють майже всі уроки модуля: обʼєкти й масиви, map і filter, незмінність, замикання, DOM і події. А наприкінці побачиш, що з цього Angular робить за тебе — і чому він узагалі потрібен.
Головна ідея: стан → render
Найпростіший спосіб не заплутатися в такому застосунку — тримати одне джерело правди. Усі дані живуть у
змінній-стані (масив задач і поточний фільтр). Сторінку ніколи не правлять по шматочку — замість цього є одна
функція render(), яка будує список із нуля за поточним станом. Будь-яка дія користувача робить
дві речі: створює нову версію стану й викликає render().
дія користувача → новий стан (незмінно!) → render() → сторінка
Запамʼятай цю схему: рівно так працює Angular, тільки render() він викликає сам.
Кроки
- Відмалюй задачі. У
render()вже є цикл. Додай класdoneвиконаним задачам (li.classList.toggle('done', task.done)). - Лічильник. Порахуй невиконані й запиши в
#counterтекстЛишилось: N. Код наприкінці виводить цей текст у консоль — завдання зарахується, коли там будеЛишилось: 1. - Додавання. В обробнику
submit: прочитай поле, обріж пробіли (trim()), порожнє не додавай. Новий стан — новий масив:tasks = [...tasks, { id: Date.now(), title, done: false }]. Очисти поле й викличrender(). - Позначка «виконано». Обробник уже ловить клік по рядку (делегування з уроку 11) і знає
id. Перемкниdoneнезмінно —map+{ ...t, done: !t.done }з уроку 6. - Фільтр. Обробник на кнопках
data-filter: збережи значення уfilterі викличrender(). А вrender()перед циклом відбери потрібні задачі черезfilter.
Перевіряй себе на кожному кроці: запускай і клікай по сторінці в рамці. Якщо щось не працює — дивись у консоль під нею: помилки там пояснені українською.
Пройди пʼять кроків вище. Завдання зарахується після кроку 2, але по-справжньому застосунок готовий, коли працюють усі пʼять.
const left = tasks.filter(t => !t.done).length; і
document.querySelector('#counter').textContent = `Лишилось: ${left}`;Підглянути рішення
Спробуй спершу сам — навіть якщо застрягнеш на якомусь кроці, пошук рішення вчить більше, ніж готовий код.
let tasks = [
{ id: 1, title: 'Звіт', done: false },
{ id: 2, title: 'Дизайн', done: true },
];
let filter = 'all';
function render() {
const list = document.querySelector('#list');
list.innerHTML = '';
const visible = tasks.filter(t =>
filter === 'all' ? true : filter === 'done' ? t.done : !t.done);
for (const task of visible) {
const li = document.createElement('li');
li.textContent = task.title;
li.dataset.id = task.id;
li.classList.toggle('done', task.done);
list.append(li);
}
const left = tasks.filter(t => !t.done).length;
document.querySelector('#counter').textContent = `Лишилось: ${left}`;
}
document.querySelector('#form').addEventListener('submit', (e) => {
e.preventDefault();
const input = document.querySelector('#title');
const title = input.value.trim();
if (!title) return;
tasks = [...tasks, { id: Date.now(), title, done: false }];
input.value = '';
render();
});
document.querySelector('#list').addEventListener('click', (e) => {
const li = e.target.closest('li');
if (!li) return;
const id = Number(li.dataset.id);
tasks = tasks.map(t => t.id === id ? { ...t, done: !t.done } : t);
render();
});
for (const btn of document.querySelectorAll('[data-filter]')) {
btn.addEventListener('click', () => {
filter = btn.dataset.filter;
render();
});
}
render();
Зверни увагу на стрілку в останньому циклі: кожна памʼятає свою кнопку btn — це замикання з уроку 7.
Що з цього Angular зробить за тебе
| Тут, руками | В Angular |
|---|---|
let tasks = […] і ручний render() після кожної зміни | tasks = signal([…]) — сторінка оновлюється сама, щойно сигнал змінився |
visible, перераховане в render() | visible = computed(() => …) — перераховується лише коли треба |
createElement, textContent у циклі | @for (task of visible(); track task.id) { <li>{{ task.title }}</li> } |
classList.toggle('done', …) | [class.done]="task.done" |
addEventListener, делегування, dataset.id | (click)="toggle(task.id)" прямо на рядку |
submit, preventDefault, читання поля | форми Angular — модуль 6 |
Логіка — незмінне оновлення масиву, фільтр, лічильник — лишається тією самою, і саме її ти щойно написав. Angular забирає собі лише рутину: стежити, що змінилось, і перемальовувати сторінку. Тепер, коли ти бачив цю рутину руками, модуль 2 читатиметься зовсім інакше.
- Нову задачу можна додати, порожню — ні; поле після додавання очищується.
- Клік по задачі перемикає «виконано», і лічильник одразу оновлюється.
- Фільтри «Усі», «Активні», «Виконані» показують правильні задачі.
- Стан змінюється лише новими масивами й обʼєктами — без
pushі безtask.done = ….