Signals и изменение локального состояния
Статический список полезен для знакомства с шаблоном, но диспетчер ожидает реакцию на действие. Рассмотрим локальное изменение статуса rq-101: нажатие кнопки переводит заявку на проектор из новой в работу. Это учебная операция в памяти браузера. Мы ещё не отправляем PATCH и не объявляем данные сохранёнными. Результат главы — понятная связь между изменением signal и участком шаблона, который читает его значение.
Signal хранит значение и позволяет Angular отслеживать чтение этого значения. В нашем случае значением будет массив заявок. Когда компонент читает tickets() в шаблоне, он получает текущую коллекцию. Скобки существенны: tickets — объект реактивного доступа, а вызов возвращает массив. Запись tickets.length относится к самой функции, а не к числу заявок. Для отображения количества сначала нужно получить значение или, позднее, построить computed.
Кто владеет изменением
Создадим signal из копии fixtures. Копирование отделяет изменяемое учебное состояние от исходного набора. Если редактировать сами fixtures, другой пример, который импортирует этот массив, получит уже изменённые записи. Это затруднит понимание состояния до и после действия. В начале курса удобно иметь неизменный образец и отдельную рабочую коллекцию. Конечный fixture API использует ту же границу: его база получает structuredClone и живёт самостоятельно.
Ниже фрагмент полей и метода класса App. Для второго учебного состояния сохраните декоратор первой главы, добавьте signal в импорт из @angular/core, замените tickets новым полем и вставьте start. В цикле шаблона используйте tickets(), а рядом с заявкой добавьте button с (click)="start(ticket.id)". Весь конечный проект доступен в архиве, но показанный метод относится к локальному этапу и в HTTP-редактор не переносится.
readonly tickets = signal(structuredClone(fixtures));
start(id: string) {
this.tickets.update(rows => rows.map(row =>
row.id === id ? {...row, status: 'active' as const} : row
));
}
Метод принимает идентификатор, поэтому действие не зависит от текущего места карточки. update получает прежний массив, map строит следующий, а условие выбирает нужную запись. Для выбранной заявки создаётся новый объект с сохранением остальных полей. Остальные объекты можно оставить прежними, поскольку их содержимое не меняется. Такое обновление явно показывает Angular новое значение массива и одновременно сохраняет предметный смысл: идентификатор проекта остаётся rq-101.
Ожидается, что после нажатия «Взять в работу» у rq-101 подпись изменится на «В работе», а rq-102 и rq-103 сохранят статусы. Перезагрузка восстановит исходные fixtures. Номер version здесь намеренно не увеличивается: он будет означать подтверждённую версию API, а не количество локальных щелчков. Если сейчас начать увеличивать его как счётчик интерфейса, позднее окажется трудно отделить черновик от серверной основы. Учебное действие меняет только отображаемый статус.
Мутация внутри массива
Сравните выбранный приём с присваиванием tickets()[0].status = 'active'. Такое присваивание меняет вложенный объект, но не задаёт signal новое значение. Оно также обращается к позиции, которая может измениться после сортировки. В результате реактивное уведомление и предметная адресация становятся неявными. Не стоит устранять проблему случайным повторным set того же массива: сначала решите, какая новая коллекция должна представлять результат операции, и создайте её непосредственно.
Readonly-доступ к signal не делает глубоко неизменными все вложенные объекты. Это важно при будущем выделении сервиса: потребитель может не иметь метода set, но всё ещё получить массив и попытаться изменить его элемент. Поэтому граница записи — одновременно API сервиса и соглашение о немутирующих обновлениях. Для учебной модели достаточно не отдавать публичные методы произвольной записи и изменять данные только через названные операции. Полноценные persistent data structures здесь не требуются.
Взаимодействие через (click) передаёт событие из шаблона методу класса. Имя метода выражает действие, а не низкоуровневую работу с DOM. Компонент не ищет карточку через querySelector и не заменяет её текст вручную: он обновляет данные, после чего представление рассчитывается из текущего значения. Благодаря этому ту же коллекцию позднее смогут читать счётчик, фильтр и дочерние строки. Не понадобится отдельно исправлять каждую надпись после одного действия.
Повторное действие и область состояния
Если повторно нажать кнопку у уже активной заявки, map снова построит массив с тем же предметным статусом. В интерфейсе ожидается прежняя подпись. Это ещё не доказательство идемпотентности серверной операции: локальный метод не имеет времени обновления, журнала и version. Повтор HTTP PATCH может иметь другой смысл, особенно при старой основе. Ограничение полезно назвать сейчас, чтобы простой визуальный эффект не превратился в обещание будущего протокола.
Попробуйте передать start несуществующий id. Условие не найдёт совпадение, и все записи останутся прежними. Для локального упражнения такое поведение допустимо; конечный API сообщит 404 при чтении или изменении отсутствующей заявки. Различие происходит из назначения границ. Метод списка работает с известными ему элементами, а сервер принимает внешний адрес и обязан объяснить отсутствие. Нельзя переносить молчаливое поведение локального массива в публичный контракт без отдельного решения.
Состояние signal принадлежит экземпляру App. Если создать другой независимый экземпляр такого компонента, у него будет своя копия списка. Это удобно для изолированного упражнения, но позднее список и карточка должны видеть согласованные данные. Тогда понадобится сервис с понятной областью предоставления или HTTP API. Пока же проверьте три связи: чтение вызывается как функция, запись создаёт новое значение, а действие адресует запись по id. Они составляют основу следующего вычисляемого фильтра.
В событии используется именно id текущей строки, а не индекс исходного массива. После появления фильтра visible первая показанная заявка может соответствовать третьей исходной записи. Метод, который безоговорочно меняет tickets[0], выполнил бы действие над проектором даже после выбора другой карточки. Следовательно, устойчивый предметный адрес нужен не только Router: он уже определяет корректность локального обновления. Это позволяет сохранить один обработчик start при изменении порядка, количества и способа отображения строк.
Статус примера — not_run. Зависимости, приложение, SQL, HTTP и тесты не запускались. Описанные ответы и изменения интерфейса ожидаемые; чтение документации не является проверкой выполнением. Полная конечная версия находится в архиве исходников, а её ограничения и действия читателя — в README и CONTRACT.md.
Перейдите к оглавлению серии или общему маршруту интерактивной веб-разработки. Механизм этой главы сверяется с официальным первичным источником; конкретные версии и непроверенные части перечислены в договоре курса.