Выделите состояние задач в сервис-хранилище, опишите модели данных и подключите их через Dependency Injection.
Коротко о главном. Представьте, что вы строите приложение-список дел (TaskFlow) вроде записной книжки задач.
Task и TaskDraft@Injectable({ providedIn: 'root' })signalinject()Контекст. Прежде чем писать код, договоримся, из каких частей состоит задача. Эти «чертежи» помогают Angular понимать, какие данные вы храните, и подсказывать ошибки заранее.
Task — как готовый билет: у него есть свой номер (id), название, отметка completed («сделано ли») и важность. А TaskDraft — это черновик анкеты без номера: вы ещё не отдали его, поэтому номер сервер выдаст сам позже.
interface Task {
id: number; // номер задачи (его выдаёт сервер)
title: string; // название задачи
completed: boolean; // true = сделано, false = ещё нет
priority: 'low' | 'medium' | 'high'; // важность
}
// Черновик — без id, его выдаёт сервер
// Omit<Task, 'id'> значит: «возьми всё из Task, но убери поле id»
type TaskDraft = Omit<Task, 'id'>;
interface Task — описываем, из чего состоит готовая задача.completed: boolean — поле статуса. Именно completed, а не done (слово done мы оставим только для фильтра «показать выполненные»).type TaskDraft = Omit<Task, 'id'> — делаем «урезанную» копию без номера. Когда пользователь создаёт задачу, номер ей присвоит сервер.Контекст. Место, где живёт список задач и правила его изменения. Компоненты (страницы) будут обращаться сюда, а не хранить задачи у себя.
providedIn: 'root' — значит «поставь её в холле, видно всем»). Внутри — закрытый бак с кофе (приватный signal): открыть его руками нельзя, но есть кнопки «налить» и «включить» (методы). Снаружи — только смотровое окно (asReadonly()) и табло со счётчиком чашек (computed).
import { Injectable, signal, computed, inject } from '@angular/core';
// @Injectable + providedIn: 'root' = «одна кофеварка на этаже»
@Injectable({ providedIn: 'root' })
export class TaskService {
// Приватный сигнал — закрытый бак. Менять можно только изнутри класса.
private readonly _tasks = signal<Task[]>([
{ id: 1, title: "Создать сервис", completed: true, priority: "high" },
{ id: 2, title: "Подключить DI", completed: false, priority: "low" }
]);
// Наружу — только для чтения (смотровое окно)
readonly tasks = this._tasks.asReadonly();
// computed — табло, пересчитывается само при изменении списка
readonly doneCount = computed(() =>
this._tasks().filter(t => t.completed).length
);
// Метод: переключить статус задачи (сделано <-> не сделано)
toggle(id: number) {
this._tasks.update(list =>
list.map(t =>
t.id === id ? { ...t, completed: !t.completed } : t
)
);
}
// Метод: добавить новую задачу из черновика
add(draft: TaskDraft) {
const nextId = Math.max(0, ...this._tasks().map(t => t.id)) + 1;
this._tasks.update(list => [...list, { id: nextId, ...draft }]);
}
}
@Injectable({ providedIn: 'root' }) — Angular создаёт один экземпляр сервиса на всё приложение.private _tasks = signal(...) — список задач лежит внутри, снаружи его не перезаписать.tasks = asReadonly() — компоненты читают, но не портят данные.doneCount — считает, сколько задач с completed: true. Пересчёт идёт автоматически.toggle(id) — находит задачу по id и переворачивает её completed.add(draft) — берёт черновик без номера, сам вычисляет новый id и добавляет в список.Контекст. Страница (компонент) хочет показать задачи. Ей нужен сервис — и вот тут срабатывает Dependency Injection.
inject(TaskService) говорит: «дай мне сервис задач, я знаю, как с ним работать». Регистрировать его вручную не нужно.
@Component({
selector: 'app-task-page',
template: `
<p>Выполнено: {{ service.doneCount() }}</p>
@for (task of service.tasks(); track task.id) {
<app-task-item [task]="task"
(toggle)="service.toggle($event)" />
}
`
})
export class TaskPage {
// inject() — нам «приносят» готовый сервис, регистрировать не надо
protected readonly service = inject(TaskService);
}
inject(TaskService) — Angular сам находит сервис (тот самый, с providedIn: 'root') и отдаёт его.service.doneCount() показывает число выполненных, а @for рисует каждую задачу списком.service.toggle(...) — то есть меняет состояние через метод сервиса.completed — это поле задачи (сделана или нет). А слово done мы используем только как значение фильтра, например 'all' | 'active' | 'done' — «показать все / активные / выполненные». Путать их не надо.Контекст. То же самое хранилище задач, но на чистом JavaScript — чтобы вы потрогали логику руками, без Angular. Здесь «сервис» — это объект с приватным состоянием и публичными методами.
toggle (переключить) и add (добавить). Нажимаете — и «табло» перерисовывается.
tasks спрятана внутри функции — снаружи её не изменить напрямую.doneCount() считает задачи с completed: true.toggle(id) меняет статус и перерисовывает экран через render().add(draft) берёт черновик без id и добавляет новую задачу.completed: true на false у первой задачи и нажмите «Запустить» — счётчик станет 0.providedIn: 'root' удобнее ручного провайдера?TaskDraft и почему в нём нет id?Практика к уроку 6. Далее: Практика к уроку 7 →
| Термин | Жизненная аналогия | Коротко |
|---|---|---|
| Сервис | Общая кофеварка на этаже | Место, где живёт список задач и правила его изменения — одно на всё приложение. |
| @Injectable | Паспорт кофеварки: «поставь меня в холле» | Пометка, что Angular может создать сервис один раз и отдавать его всем. |
| DI (внедрение зависимостей) | Кто-то приносит готовый инструмент на стол | Метод inject() сам отдаёт сервис — регистрировать руками не нужно. |
| Локальное состояние | Липкий листок у тебя на столе | Данные живут только внутри одного компонента и не видны остальным. |
| Серверное состояние | Доска объявлений | Общие данные, которые приходят с сервера и видны всем пользователям. |
| Task | Готовый билет с номером | Задача с id и полем completed (сделана или нет). |
| TaskDraft | Черновик анкеты без номера | Задача без id — номер ей выдаст сервер при сохранении. |
1. Сервис с providedIn: 'root' создаётся один раз на всё приложение. Верно или неверно?
Ответ: Верно. Как одна кофеварка на этаже — её поставили в холле, и пользуются ей все сотрудники, а не каждый заводит свою.
2. Метод inject() нужно регистрировать вручную, прежде чем использовать. Верно или неверно?
Ответ: Неверно. Внедрение зависимостей — это когда готовый инструмент приносят на стол сами; вам не нужно идти в кладовку и собирать его самому.
3. В модели TaskDraft уже есть поле id. Верно или неверно?
Ответ: Неверно. Это черновик анкеты без номера; заполненный билет Task получит id, когда сервер его выдаст.