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

Router и адрес отдельной заявки

Карточка заявки должна иметь адрес, который можно открыть из списка или передать коллеге. Состояние «выбран rq-101» внутри App не решает эту задачу: после перезагрузки его может не быть, а ссылка на общий кабинет не сообщает нужный объект. Добавим Router и маршруты /tickets и /tickets/:id. Идентификатор станет частью адреса, тогда как содержание карточки останется данными, которые нужно отдельно прочитать по этому идентификатору.

Мы пока разбираем синхронный выбор локальных fixtures. Конечный DetailPage уже использует API, но смысл параметра маршрута одинаков на обоих этапах. Адрес не хранит заявку целиком и не гарантирует её существование. /tickets/rq-999 имеет допустимую форму, однако исходная коллекция такой записи не содержит. Поэтому успешный выбор маршрута и успешное получение предметного объекта — два независимых результата, которые интерфейс должен объяснять отдельно.

Таблица переходов

Ниже фрагмент routes до перехода к lazy loading. Для этого учебного состояния ListPage и DetailPage импортируются непосредственно, а окончательный app.routes.ts заменит component на loadComponent. Корневой пустой путь перенаправляет на tickets; pathMatch full ограничивает это правило целым адресом. Без такого ограничения слишком широкое перенаправление способно перехватить пути, которые должны попасть в другие записи. Общий fallback помещён последним, чтобы сначала проверить предметные маршруты.

export const routes: Routes = [
  {path:'', pathMatch:'full', redirectTo:'tickets'},
  {path:'tickets', component:ListPage},
  {path:'tickets/:id', component:DetailPage},
  {path:'**', redirectTo:'tickets'}
];

В App требуется RouterOutlet — место, куда Router помещает выбранную страницу. RouterLink в ссылке строки описывает переход без ручного формирования HTML и без прямой замены window.location. Компонентный imports должен включать соответствующую директиву. Настройка provideRouter в app.config.ts предоставляет сам Router на уровне приложения. Эти три части выполняют разные обязанности: таблица решает выбор, outlet задаёт место представления, ссылка сообщает намерение перейти на адрес.

Ожидается, что ссылка «Заменить проектор» приведёт к /tickets/rq-101 и покажет страницу карточки вместо списка внутри общего main. Шапка и переключатель роли сохранятся, потому что App остаётся оболочкой. При возврате на /tickets Router снова создаст страницу списка. Роль сохранится в root Session, но локальные поля уничтоженного компонента нельзя считать сохранёнными. Позднее фильтр будет восстанавливаться из query-параметров, а редактор получит правила работы с черновиком.

Параметр id и жизненный цикл

ActivatedRoute предоставляет состояние активного маршрута. Snapshot удобен, когда значение действительно нужно один раз, однако переход rq-101 → rq-102 может использовать тот же экземпляр страницы при другой величине параметра. Тогда чтение snapshot только в constructor оставит старую заявку. В конечном DetailPage используется поток paramMap с выделением id и distinctUntilChanged. Это сообщает компоненту изменения адреса независимо от того, был ли экземпляр создан заново.

Идентификатор из адреса является внешней строкой. Даже если ссылки списка используют только известные rq-101…103, пользователь может вручную ввести другое значение. На локальном этапе поиск fixtures по id возвращает запись либо отсутствие. API затем проверит формат и вернёт 400 для неверного id или 404 для отсутствующей заявки. Интерфейс не должен пытаться лечить любой неправильный адрес выбором первой строки: такой fallback показал бы человеку чужой объект под ошибочной ссылкой.

Перенаправление wildcard в архиве ведёт к списку. Это упрощённое решение для неизвестной страницы приложения, например /reports. Оно отличается от 404 конкретной заявки: путь /tickets/rq-999 совпадает с карточкой, а её данные отсутствуют. Для производственного кабинета можно добавить отдельный компонент неизвестного маршрута, сохранив исходный URL и сообщение. Здесь важнее удержать различие между неизвестной страницей и неизвестным предметным идентификатором внутри известной страницы.

Адреса и данные

Не помещайте title, version и весь объект Ticket в query только ради открытия карточки. Адрес быстро станет длинным, а данные устареют после изменения другим диспетчером. Идентификатор позволяет обратиться к актуальному источнику. Version принадлежит полученной основе редактора, а не доказательству существования страницы. Если злоумышленник поменяет version в адресе, это не должно предоставить ему право перезаписать заявку: сервер будет сравнивать значение PATCH с состоянием базы.

Внешняя перезагрузка /tickets/rq-101 требует от сервера, который отдаёт Angular, поддержки маршрутов SPA. Локальный Angular dev-server рассчитан на такой способ работы; production hosting понадобится настроить отдельно. Статическая загрузка HTML сама не реализует серверный API /api/tickets. В архиве proxy.conf.json относится к локальному dev-server и передаёт только API-запросы соседнему Nest. Поэтому не используйте удачное внутреннее переключение страниц как подтверждение готовой конфигурации публикации.

Ссылки остаются семантическими anchor-элементами. Пользователь может открыть карточку привычным способом, а фокус обозначен общей CSS-рамкой. Кнопка подходит для операции, ссылка — для перехода. Если превратить каждую карточку в div с обработчиком click, придётся самостоятельно повторить клавиатурное поведение и смысл ссылки. В небольшом упражнении это кажется косметикой, но доступность и ожидаемые действия браузера являются частью результата маршрутизации.

Для самостоятельного разбора сравните три адреса: /tickets, /tickets/rq-101 и /tickets/rq-999. Первый выбирает список, второй — карточку и существующий объект, третий — карточку и ожидаемый отказ чтения. Затем поменяйте вручную rq-101 на rq-102 без изменения названия маршрута. Компонент должен заново связать данные с id. Эта проверка показывает, что route configuration выбирает тип страницы, а параметр выбирает объект внутри неё.

На следующем шаге страницы станут ленивыми. Адреса и предметная модель при этом сохранятся. Изменится способ получения класса компонента, а не договор ссылки на заявку. Такой переход полезно делать после обычной маршрутизации: вы уже понимаете, что Router выбирает, и можете отдельно рассмотреть, когда загружается код. Не требуется одновременно менять идентификаторы, структуру данных и всю оболочку только ради одного нового механизма.

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

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