Практическая работа к уроку 3 (занятие 2)

Компоненты и шаблоны Angular

Соберите экран списка задач из компонентов: standalone-компонент, входы, control flow и связывание классов.

Что вы узнаете

В этом уроке мы строим экран списка дел (как в приложении «Заметки» или «Дела»). Представьте, что вы собираете машину: отдельные детали потом соединяются в одну работающую машину. Так же и в Angular — из маленьких «деталей» (компонентов) собирается весь экран.

Задание 1. Компонент TaskItem

Создайте standalone-компонент, который получает задачу через вход и выводит её.

Контекст. В приложении TaskFlow каждая задача — это одна строчка списка. Чтобы не писать одно и то же много раз, мы делаем деталь-«однострочник» TaskItem и используем её для каждой задачи.

💡 Аналогия. Компонент — как деталь конструктора Lego. Вы собираете один кирпичик (компонент), а потом ставите его много раз, чтобы получился забор или дом. Вход input() — как почтовый ящик у этой детали: в него «приходит» письмо с данными (какая задача показать), а деталь сама решает, как это письмо отобразить.

Пошаговый разбор:

  1. import { Component, input } — берём из Angular два инструмента: сам компонент и способ сделать вход.
  2. @Component({...}) — это «паспорт» детали: как её зовут (selector) и что она рисует (template).
  3. standalone: true — значит, компонент самодостаточный, ему не нужна отдельная «коробка-модуль», чтобы работать.
  4. task = input.required<Task>() — объявляем почтовый ящик: сюда обязательно должны положить задачу типа Task.
  5. В шаблоне {{ task().title }} — берём из письма заголовок и показываем на экране.

Что увидит пользователь: на экране появится строчка вида «Изучить компоненты» (пока без оформления).

import { Component, input } from '@angular/core';

@Component({
  selector: 'app-task-item',  // имя детали: <app-task-item></app-task-item>
  standalone: true,           // компонент работает сам по себе
  template: `
    <li [class.done]="task().completed">  // табличка: если done — добавим класс
      {{ task().title }}                  // покажем заголовок задачи
    </li>
  `
})
export class TaskItem {
  task = input.required<Task>();  // вход-«почтовый ящик»: сюда придёт задача
}
Проверка. У выполненной задачи появляется класс done — через него можно зачеркнуть текст в CSS.

Задание 2. Список через @for и @empty

Выведите задачи с ключом track и покажите заглушку, если список пуст.

💡 Аналогия. @for — как почтовый конвейер на складе: лента прокручивается, и для каждой посылки (задачи) работник выполняет одно и то же действие — берёт деталь TaskItem и кладёт её в чемодан. track — как бирка с номером на каждой посылке: по ней понятно, какая посылка которую по счёту, и если одну заменить, не перетряхивать весь конвейер.

Пошаговый разбор:

  1. @for (task of tasks(); track task.id) — берём каждую задачу из списка tasks() по очереди.
  2. track task.id — говорим Angular: «различай задачи по их номеру id».
  3. Внутри — <app-task-item [task]="task" />: для каждой задачи ставим деталь и кладём ей в почтовый ящик эту задачу.
  4. @empty { ... } — срабатывает только если список пустой (посылок на ленте нет).

Что увидит пользователь: либо список строк задач, либо (если список пуст) надпись «Задач пока нет. Добавьте первую!».

@for (task of tasks(); track task.id) {
  <app-task-item [task]="task" />   // для каждой задачи — своя деталь
} @empty {
  <p>Задач пока нет. Добавьте первую!</p>  // если список пуст
}
Зачем track. Angular по task.id понимает, какой элемент изменился, и обновляет только его, а не весь список. Это быстрее, особенно когда задач сотни.

Задание 3. Условия @if и @switch

Покажите индикатор фильтра и разное состояние через управляющие конструкции.

💡 Аналогия. @if — как зонт: «если пошёл дождь (идёт загрузка) — раскрой зонт (покажи «Загрузка…»), иначе — спрячь». @switch — как переключатель света: выбрано одно из нескольких положений («все / открытые / закрытые»), и загорается только соответствующая надпись.

Пошаговый разбор:

  1. @if (loading()) — проверяем, идёт ли загрузка. Если да — показываем «Загрузка…».
  2. @else — если загрузка закончилась, смотрим на фильтр.
  3. @switch (filter()) — берём текущее значение фильтра и выбираем подходящий @case.
  4. Каждый @case — это один вариант: 'all', 'active' или 'done' (слово done здесь — просто название фильтра «закрытые задачи», не путайте с полем задачи).

Что увидит пользователь: пока данные грузятся — «Загрузка…»; потом — одну из надписей: «Все записи», «Открытые» или «Закрытые».

@if (loading()) {
  <p>Загрузка…</p>          // зонт раскрыт: идёт загрузка
} @else {
  @switch (filter()) {       // выбираем режим фильтра
    @case ('all')     { <p>Все записи</p> }
    @case ('active')  { <p>Открытые</p> }
    @case ('done')    { <p>Закрытые</p> }  // done — значение фильтра
  }
}

Задание 4. Связывание классов

Добавьте CSS, чтобы выполненные задачи визуально отличались.

💡 Аналогия. Class binding ([class.done]) — как табличка на двери комнаты. Если задача выполнена (на двери табличка «занято/готово»), мы вешаем класс done, и CSS красит текст серым и зачёркивает. Нет таблички — текст обычный.

Пошаговый разбор:

  1. Мы выбираем элемент с классом done и внутри него — span (сам текст задачи).
  2. text-decoration: line-through — рисует линию поверх текста (зачёркивание).
  3. color: gray — делает текст серым, чтобы выполненное казалось «неактивным».

Что увидит пользователь: выполненные задачи серые и перечёркнутые, невыполненные — чёрные и обычные.

.done span {
  text-decoration: line-through;  // зачёркиваем текст
  color: gray;                    // делаем серым
}
Вопрос. Чем [class.done]="task().completed" отличается от [ngClass]? (Подсказка: new control flow не требует отдельного импорта.)

Интерактивная песочница

Здесь — та же логика на чистом JS, чтобы увидеть, как компонент «собирает» HTML из данных:

💡 Аналогия. Песочница — как макет из картона: вы не строите настоящий дом, а собираете игрушечный, чтобы понять принцип. Здесь TaskItem — это функция, которая по данным возвращает кусок HTML, а конвейер tasks.map(...) собирает все строчки вместе.

Песочница: нажми «Запустить» — и увидишь, как из списка задач собираются строчки: выполненная задача будет серая и зачёркнутая, а остальные — обычные. Меняй код и запускай снова.
Песочница: рендеринг списка

Что увидит пользователь в песочнице: три строчки. Первая — «✓ Изучить компоненты» — серая и зачёркнутая (выполнена). Остальные две — «○ Написать TaskItem» и «○ Настроить @for» — обычные.

Проверьте себя

Вопросы.
  1. Чем [ ] отличается от ( ) в шаблоне? (Подсказка: квадратные скобки — «дай данные внутрь», круглые — «поймай событие снаружи».)
  2. Почему track важен при больших списках? (Бирка с номером помогает не пересобирать весь список.)
  3. Когда использовать @if, а когда @switch? (@if — одно «да/нет», @switch — выбор из нескольких вариантов.)

Практика к уроку 3. Далее: Практика к уроку 4 →

Куда дальше: теория компонентов — в Уроке 3; дерево компонентов — в лабораторной №4.

📖 Глоссарий

ТерминЖизненная аналогияКоротко
КомпонентДеталь конструктора LegoМаленькая «запчасть» экрана, которую можно ставить много раз.
ШаблонФорма-бланкРазметка компонента: что и где показывать на экране.
Интерполяция {{ }}Вписывание имени в бланкВставляет значение переменной прямо в текст: {{ task().title }}.
Вход input()Почтовый ящик ребёнкаЧерез него компоненту «приходят» данные снаружи.
@if / @for / @switchЗонт / почтовый конвейер / переключатель светаУсловие «да/нет», повтор для каждого элемента, выбор из нескольких вариантов.
Привязка классов [class.*]Табличка на двериВешает CSS-класс, когда выполнено условие (напр. задача готова).

🧠 Самопроверка

1. Компонент Angular похож на деталь конструктора: один и тот же компонент можно использовать много раз на экране. Верно или неверно?

2. Через input() компонент сам создаёт данные и отдаёт их родителю. Верно или неверно?

3. @switch удобнее, чем несколько @if, когда нужно выбрать один вариант из нескольких (например, фильтр «все / открытые / закрытые»). Верно или неверно?

Прогресс курса