Перейти к содержанию

Состояния страницы и доступные ошибки

Когда запросы становятся частью страницы, у пользователя появляется несколько внешне похожих состояний. Список ещё загружается, фильтр не нашёл совпадений, сервер отклонил токен или JSON оказался повреждён. Во всех случаях карточек может не быть, но причины и следующие действия различаются. Построим сообщения вокруг этих причин и рассмотрим доступность элементов. Результат — интерфейс, который объясняет состояние операции словами и не требует угадывать его по исчезновению строк или цвету.

ListState уже ограничивает допустимые ветви: loading, error и ready. Пустой список является корректным ready с items длины ноль. Поэтому empty не отдельный транспортный отказ, а результат известного фильтра. В template используется @empty внутри цикла. Это помогает сохранить важное сообщение: «По этому фильтру заявок нет». Человек может изменить q или status, вместо того чтобы бесконечно повторять успешный запрос, ожидая исправления якобы сломанной сети.

Сообщение связано с причиной

Ниже полный errorMessage из файла api.ts. Это функция представления, которая принимает unknown и возвращает пользовательский текст. HttpErrorResponse импортирован в этом же файле. Основные ветви выбираются по status, а не по свободному русскому message сервера. Контракт не требует одинаковой буквальной формулировки у разных backend-реализаций. Код VERSION_CONFLICT существует в API, однако для ограниченного набора лаборатории клиент использует известный HTTP-статус 409.

export function errorMessage(error: unknown): string {
  if (error instanceof HttpErrorResponse) {
    if (error.status === 409) return 'Заявка изменена. Черновик сохранён; загрузите актуальную основу явно.';
    if (error.status === 403) return 'Для изменения нужна роль диспетчера.';
    if (error.status === 401) return 'Сервер отклонил учебный токен.';
    if (error.status === 404) return 'Заявка не найдена.';
    if (error.status === 0) return 'Нет ответа сервера. Проверьте соединение.';
    return 'Сервер отклонил запрос.';
  }
  return error instanceof Error ? error.message : 'Не удалось выполнить действие.';
}

Для 404 ожидается «Заявка не найдена», для 403 — пояснение о роли, для 409 — предложение явно загрузить новую основу. Состояние status 0 означает отсутствие нормального HTTP-ответа на стороне HttpClient, а не обязательно выключенную PostgreSQL. Причина может относиться к соединению или другой транспортной границе. Поэтому текст предлагает проверить соединение и не рассказывает пользователю выдуманную внутреннюю историю сервера. Неизвестный серверный отказ также получает обобщённое сообщение.

Ошибки readTicket/readList являются обычными Error и сохраняют объяснение нарушения модели. Это полезно разработчику лаборатории, который сравнивает контракт двух курсов. В рабочем продукте публичное сообщение могло бы быть спокойнее, а точная диагностика попадала бы в разрешённый журнал. Здесь текст не содержит SQL, stack trace или токен, поскольку эти данные не нужны пользователю для следующего действия. Серверный фильтр Nest дополнительно скрывает неожиданные внутренние ошибки.

Объявление динамического результата

Loading и количество результатов используют role status, а отказ — role alert. Такой выбор сообщает assistive technology о динамическом изменении содержания. Он не означает, что любой длинный блок следует делать alert: чрезмерные повторные объявления мешают человеку выполнять задачу. В лаборатории сообщения короткие и возникают при значимом переходе. Фактическое чтение конкретным screen читатель не проверялось; нужный runtime-сценарий указан в README как самостоятельная проверка.

Одинаковый смысл должен быть доступен и без этих специальных ролей. Текст «Загружаем заявки…» виден всем, а errorMessage объясняет причину обычными словами. Цветная рамка дополняет сообщение, но не кодирует единственную информацию. Аналогично статусы заявок выводятся русскими словами. Если убрать CSS, читатель по-прежнему должен различить новую, активную и завершённую заявку. Это простой способ проверить, не спрятан ли предметный смысл в оформлении.

У поля поиска есть label, у select статуса — отдельный label. Формулировка placeholder не заменяет постоянную метку: она исчезает после ввода и может хуже сообщать назначение элемента. У title есть aria-describedby, связанный с правилом длины. Общий focus-visible обозначает клавиатурный фокус. Кнопки role используют aria-pressed, чтобы сообщить выбранное состояние. Эти небольшие решения работают вместе, но не позволяют объявить весь кабинет полностью проверенным на доступность без настоящего обхода.

Повтор и возвращение к задаче

Кнопка retry появляется рядом с ошибкой списка и повторяет текущий фильтр. Она не очищает q и status, поскольку такое изменение скрывало бы исходную задачу пользователя. Если запрос «проектор» не дошёл до API, человек хочет повторить его, а не получить все заявки. При 400, вызванном прямым неправильным API-вызовом, повтор без исправления данных не решит проблему. Поэтому интерфейсная retry относится к валидному запросу страницы, а транспортный контракт охватывает более широкий набор внешних клиентов.

Карточка при 409 сохраняет форму и показывает сообщение над ней. Автоматическая перезагрузка без предупреждения уничтожила бы введённый текст. Кнопка reload прямо называет замену черновика. Она доступна только когда чтение и запись не выполняются, поэтому не создаёт конкурирующую основу во время save. Для сложного продукта потребуются сравнение изменений, сохранение локального черновика или специальное разрешение конфликта. Лаборатория не реализует эти стратегии и не выдаёт простую кнопку за универсальное восстановление.

В App добавлена ссылка «К содержимому» на main с tabindex минус один. Она помогает перейти мимо шапки клавиатурой, сохраняя обычную структуру документа. Навигация между страницами не содержит отдельного автоматического переноса фокуса на новый h1; это оставшаяся задача UX. После построения и открытия проекта читателю следует проверить, где остаётся фокус после перехода и как сообщается новая страница. Наличие RouterOutlet не отвечает на этот пользовательский вопрос автоматически.

Разберите четыре ожидаемых сценария. Корректный q без совпадений даёт empty, недоступный remote API — error соединения, fixture с обычным ответом — ready, а повреждённый shape — error модели. Карточки отсутствуют во всех первых трёх промежуточных ситуациях, но текст разный. Затем повторите анализ для клавиатуры: можно ли найти поиск, применить фильтр, открыть ссылку и активировать retry. Такая проверка соединяет состояние данных с реальным способом выполнения задачи.

Никакие screen читатель, HTTP-запросы или измерения контраста здесь не запускались. Мы описали ожидаемые свойства разметки и конкретные оставшиеся проверки. В следующем уроке форма отправит PATCH, и точное различие между действием, ожиданием и подтверждением станет ещё важнее. Существующая модель сообщений уже позволяет дать человеку честный ответ после отказа, не притворяясь, что отсутствие красной рамки у title означает успешную запись всей заявки.

Статус примера — not_run. Зависимости, приложение, SQL, HTTP и тесты не запускались. Описанные ответы и изменения интерфейса ожидаемые; чтение документации не является проверкой выполнением. Полная конечная версия находится в архиве исходников, а её ограничения и действия читателя — в README и CONTRACT.md.

Перейдите к оглавлению серии или общему маршруту интерактивной веб-разработки. Механизм этой главы сверяется с официальным первичным источником; конкретные версии и непроверенные части перечислены в договоре курса.