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

Ленивые страницы и границы загрузки

Маршруты кабинета уже определяют, какой экран показывает Router. Теперь отделим выбор экрана от первоначальной загрузки его кода. Список понадобится сразу, а редактор заявки может вообще не открыться за сеанс. LoadComponent позволяет описать получение standalone-компонента через динамический import. Результат главы — ленивые границы страниц с прежними адресами и прежним общим состоянием учебной сессии.

На трёх заявках разница в размере приложения не является целью эксперимента. Мы не выполняли сборку и не измеряли загрузку. Ленивый маршрут рассматривается как архитектурная возможность: код страницы не требуется непосредственно импортировать в таблицу маршрутов. Реальные файлы chunk, число сетевых запросов и время появления экрана зависят от сборки и окружения. Их нельзя вывести из наличия одного слова loadComponent без отдельного воспроизведения.

Динамическое получение компонента

Ниже полный конечный src/app/app.routes.ts. Он заменяет прямые ссылки component из предыдущей главы. Динамический import возвращает модуль, после чего then выбирает конкретный экспорт класса. Имена ListPage и DetailPage должны совпасть с exports соответствующих файлов. Отсутствующий экспорт не исправляется правильным текстом пути: Router должен получить именно компонент, а не весь объект модуля. TypeScript помогает обнаружить часть таких несоответствий, но здесь компиляция не проводилась.

import { Routes } from '@angular/router';
export const routes: Routes = [
  {path:'',pathMatch:'full',redirectTo:'tickets'},
  {path:'tickets',loadComponent:() => import('./list-page').then(module => module.ListPage)},
  {path:'tickets/:id',loadComponent:() => import('./detail-page').then(module => module.DetailPage)},
  {path:'**',redirectTo:'tickets'}
];

Ожидаемое поведение навигации не меняется: /tickets открывает список, /tickets/rq-101 — карточку. Разница касается получения класса страницы. App остаётся заранее известной оболочкой и содержит RouterOutlet. Root Session также не переносится внутрь ленивой страницы. Поэтому учебная роль продолжает сохраняться при переходах. Если одновременно добавить отдельного provider Session в маршрут карточки, возникнет другая область DI; это было бы самостоятельным изменением, которое lazy loading не требует.

Пути import относительны файлу таблицы маршрутов и известны сборщику. Не стройте произвольный путь из title заявки или полученного HTTP id. Данные выбирают экземпляр содержимого внутри известного компонента, а не новый программный модуль. В противном случае предметная строка начнёт управлять устройством поставки кода. Для нашего кабинета есть два известных класса страницы независимо от количества заявок: список и редактор одной записи.

Загрузка кода и загрузка данных

Важно различать отсутствие компонента и отсутствие Ticket. Пока Router получает код страницы, её HTTP-запрос ещё может не начаться. После создания DetailPage начинается чтение заявки, которое может завершиться 404 или сетевым отказом. Сообщение «Загрузка заявки» относится ко второму этапу. Оно не доказывает состояние загрузки chunk. Отдельный индикатор перехода Router можно добавить при развитии оболочки, но он не должен скрывать ошибку API и подменять её ожиданием кода.

Аналогично HTTP cache не означает, что ленивый модуль уже получен, а загруженный модуль не означает, что заявки сохранены. Браузер может повторно использовать программный ресурс, тогда как данные необходимо запросить заново. В лаборатории fixture хранит записи в памяти root-сервиса, который живёт после перехода; удалённый Nest хранит их в PostgreSQL. Эти способы жизни данных определены отдельно от lazy route и должны оставаться понятными читателю при разборе ожидаемого результата.

Сетевой сбой получения chunk не исправляется кнопкой «Повторить» списка, если сама страница ещё не создана. Конечная версия не содержит самостоятельного обработчика ошибок навигации или offline-кэширования модулей. Это граница архива, а не обещание автоматического восстановления Angular. При передаче проекта такую непроверенную часть следует назвать. Для развития можно наблюдать Router events и показывать сообщение в оболочке, которая уже загружена; приём требует отдельной политики повторной навигации.

Что стоит переносить за границу

Ленивой делается страница с самостоятельной задачей, а не каждый маленький текстовый элемент. TicketRow нужен списку и может попасть вместе с ним в доступный ему код. Простое разделение каждой подписи на отдельный dynamic import усложнит граф зависимостей и пользовательское ожидание без очевидной пользы. Мы выбираем уровень списка и редактора, потому что это реальные точки навигации. Позднее отдельный большой отчёт мог бы стать третьей такой границей.

Общие типы Ticket и Status расположены в model.ts. Они не требуют загрузки отдельной страницы для понимания контракта. Однако в этом же файле есть fixtures, поэтому сама структура источников ещё не является идеальным production-разделением по размеру. Архив решает учебную задачу читаемого проекта. При исследовании bundle пришлось бы посмотреть, какие exports устранены сборщиком и какие данные включены в chunks. Без сборки нельзя обещать, что файл модели имеет нулевой runtime-вес.

Прямой импорт DetailPage в другом заранее загружаемом файле способен изменить смысл ленивой границы. Например, если App начнёт использовать редактор в собственном template и импортирует его класс, один loadComponent в routes уже не описывает все пути доставки компонента. Поэтому при обзоре проекта проверяйте граф фактического использования. В конечной оболочке App нет такой зависимости: она знает ссылки, outlet, Session и config, а страницы объявлены только в таблице маршрутов.

Сравните учебную модель двух вкладок. Первая открывает только список, вторая — карточку непосредственно по URL. На уровне приложения обе навигации должны быть допустимы: lazy route не требует сначала посетить список и получить от него компонент или Ticket. DetailPage берёт id из ActivatedRoute и читает источник самостоятельно. Такая независимость даёт ссылке практический смысл. Вариант, где карточка существует лишь после передачи объекта из списка, нарушил бы прямое открытие.

При самостоятельном воспроизведении читатель может открыть Network, перейти к карточке и изучить реальные файлы поставки. В этой сессии такой сценарий не выполнялся, поэтому имена chunks и времена не приведены. Педагогический результат уже определён: Router получает страницу через объявленный динамический import, а API читает данные по отдельному контракту. Далее мы сосредоточимся на форме редактора и сохраним ленивые маршруты без изменений.

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

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