Angularшлях до Strong Junior
Модулі / JavaScript: основа / Практика: список задач на чистому JS
🔥 0

Фундамент Ф1 · урок 12 з 12

Практика: список задач на чистому JS

Час зібрати все разом. Ти напишеш маленький застосунок — список задач — на чистому JavaScript: додавання, позначка «виконано», фільтр і лічильник. Тут працюють майже всі уроки модуля: обʼєкти й масиви, map і filter, незмінність, замикання, DOM і події. А наприкінці побачиш, що з цього Angular робить за тебе — і чому він узагалі потрібен.

Головна ідея: стан → render

Найпростіший спосіб не заплутатися в такому застосунку — тримати одне джерело правди. Усі дані живуть у змінній-стані (масив задач і поточний фільтр). Сторінку ніколи не правлять по шматочку — замість цього є одна функція render(), яка будує список із нуля за поточним станом. Будь-яка дія користувача робить дві речі: створює нову версію стану й викликає render().

дія користувача  →  новий стан (незмінно!)  →  render()  →  сторінка

Запамʼятай цю схему: рівно так працює Angular, тільки render() він викликає сам.

Кроки

  1. Відмалюй задачі. У render() вже є цикл. Додай клас done виконаним задачам (li.classList.toggle('done', task.done)).
  2. Лічильник. Порахуй невиконані й запиши в #counter текст Лишилось: N. Код наприкінці виводить цей текст у консоль — завдання зарахується, коли там буде Лишилось: 1.
  3. Додавання. В обробнику submit: прочитай поле, обріж пробіли (trim()), порожнє не додавай. Новий стан — новий масив: tasks = [...tasks, { id: Date.now(), title, done: false }]. Очисти поле й виклич render().
  4. Позначка «виконано». Обробник уже ловить клік по рядку (делегування з уроку 11) і знає id. Перемкни done незмінно — map + { ...t, done: !t.done } з уроку 6.
  5. Фільтр. Обробник на кнопках 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 = ….