1. Как работает ленивая загрузка модулей в Angular
Давайте представим, что Angular-приложение — это супермаркет, а ваши пользователи — покупатели. Если при первом входе вы заставите их ждать, пока откроются ВСЕ отделы (даже те, куда они никогда не зайдут), они вряд ли будут в восторге. Ленивая загрузка — это когда вы открываете только главный вход и ближайшие витрины, а остальные отделы "разворачиваете" только тогда, когда покупатель туда реально заходит.
Зачем это нужно?
- Уменьшает размер главного (initial) бандла, ускоряя первую загрузку приложения.
- Позволяет разделять код на логические части, которые загружаются только по требованию.
- Улучшает пользовательский опыт: быстрее старт, меньше трафика, меньше стресса для слабых устройств.
Немного теории
В Angular ленивая загрузка реализуется на уровне feature-модулей (фичевых модулей). Вы создаёте отдельный модуль для функционального раздела (например, AdminModule, UserModule), настраиваете для него собственные маршруты, а в основном роутинге приложения указываете, что этот модуль должен загружаться только при обращении к определённому пути.
Пример: базовая настройка ленивой загрузки
Создаём feature-модуль
ng generate module admin --route admin --module app.module
Эта команда не только создаст модуль, но и сразу добавит его в маршруты с ленивой загрузкой!
Как выглядит запись в app-routing.module.ts
const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
},
// другие маршруты...
];
Что тут происходит?
Когда пользователь заходит по адресу /admin, Angular динамически загружает (импортирует) модуль AdminModule.
До этого момента JS-код этого модуля вообще не попадает в главный бандл!
Как это выглядит в браузере
- Открываете / — грузится только основной бандл.
- Открываете /admin — в консоли (Network) видите, что подгружается отдельный чанк (файл JS), отвечающий за AdminModule.
Визуальная схема
[Главный бандл] ---запрос---> [HomeComponent, AboutComponent]
|
| (пользователь кликает на "Admin")
|
+---запрос---> [admin.*.js: AdminModule, AdminComponent, ...]
2. Ленивая загрузка Standalone-компонентов (Angular 15+)
С появлением Standalone-компонентов (Standalone Components) в Angular 15 стало возможным лениво загружать не только модули, но и отдельные компоненты! Это особенно удобно, если вы строите приложение без NgModules (или хотите "по-современному").
Пример: ленивый Standalone-компонент
Создаём Standalone-компонент
ng generate component lazy-page --standalone
Добавляем маршрут с ленивой загрузкой компонента
const routes: Routes = [
{
path: 'lazy',
loadComponent: () => import('./lazy-page/lazy-page.component')
.then(c => c.LazyPageComponent)
}
];
Обратите внимание:
Вместо loadChildren используем loadComponent.
Angular загрузит компонент только при переходе на /lazy.
Когда использовать ленивую загрузку компонентов?
- Когда у вас небольшие разделы/страницы, которые не требуют отдельного модуля.
- Когда вы хотите максимально упростить структуру приложения.
- Для диалогов, больших форм, "тяжёлых" страниц, которые редко используются.
3. Организация структуры Angular-приложения: best practices
Почему структура имеет значение?
- Лёгкая масштабируемость: проект растёт — структура не рассыпается.
- Удобство навигации по коду для команды (и для вас спустя месяц!).
- Простота внедрения ленивой загрузки и разделения ответственности.
Типичная структура среднего приложения
src/
app/
core/ # Сервисы, singleton-логика, глобальные guard-ы
shared/ # Переиспользуемые компоненты, pipes, директивы
features/ # Фичевые модули или standalone-компоненты
admin/
admin.module.ts
admin-routing.module.ts
pages/
components/
user/
...
app-routing.module.ts
app.component.ts
...
assets/
environments/
Краткое описание
- core/ — глобальные singleton-сервисы, глобальные guard-ы, перехватчики (interceptors), глобальные утилиты.
- shared/ — переиспользуемые компоненты (кнопки, инпуты), pipes, директивы, которые не зависят от бизнес-логики.
- features/ — разделы приложения, каждый из которых может быть лениво загружен.
- app-routing.module.ts — центральное место для настройки маршрутов и ленивой загрузки.
Особенности для Standalone-подхода
Если вы строите приложение на Standalone-компонентах, структура может быть чуть проще:
src/
app/
core/
shared/
pages/
home/
home.component.ts
admin/
admin.component.ts
...
app.routes.ts
app.component.ts
4. Практика: добавляем ленивую загрузку в учебное приложение
Давайте продолжим развивать наше учебное приложение (например, список задач с админкой).
Генерируем модуль и компонент для админки
ng generate module admin --route admin --module app.module
ng generate component admin/dashboard --module=admin
Добавляем ленивую загрузку в маршруты
В app-routing.module.ts:
const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
},
// остальные маршруты
];
В admin-routing.module.ts:
const routes: Routes = [
{ path: '', component: DashboardComponent }
];
Проверяем результат
- Собираем приложение.
- Открываем / — код админки не грузится.
- Открываем /admin — загружается дополнительный JS-файл.
Добавляем ленивый Standalone-компонент
ng generate component about-page --standalone
В app-routing.module.ts или app.routes.ts:
const routes: Routes = [
{
path: 'about',
loadComponent: () => import('./about-page/about-page.component').then(c => c.AboutPageComponent)
}
];
5. Полезные нюансы
Особенности и ограничения ленивой загрузки
- Ленивая загрузка работает только для маршрутов. Нельзя лениво загрузить просто сервис или pipe — только модули/компоненты, связанные с маршрутом.
- Сервисы, предоставленные в лениво загружаемом модуле, по умолчанию изолированы от основного инжектора (если не указан providedIn: 'root'). Это позволяет делать, например, отдельные сессии для админки и пользователя.
- Guard-ы, резолверы и другие сервисы также могут быть определены в ленивых модулях — они будут работать только внутри этого раздела.
- Пути в ленивых модулях всегда относительно своего корня! Не забывайте это при настройке вложенных маршрутов.
Как понять, что ленивая загрузка работает?
- В девелоперских инструментах браузера (вкладка Network) при переходе на ленивый маршрут должен загружаться новый JS-файл.
- В отчёте сборки (например, после ng build --stats-json и анализа через Webpack Bundle Analyzer) видно, что код разделён на чанки.
- Если всё приложение грузится одним файлом — что-то пошло не так: проверьте конфигурацию маршрутов!
Best practices по структуре и ленивой загрузке
- Делите приложение на feature-модули/standalone-компоненты по бизнес-логике, а не по техническим признакам.
- Используйте ленивую загрузку для крупных разделов, которые не нужны сразу при старте. Например: админка, личный кабинет, разделы для разных ролей.
- Переиспользуемые компоненты и сервисы держите в shared/core, а не в feature-модулях. Это ускоряет старт и предотвращает дублирование кода.
- Старайтесь не делать слишком глубокую вложенность модулей — это усложняет навигацию и сборку.
- Документируйте структуру проекта и правила разделения кода для команды.
- Проверяйте результат ленивой загрузки после каждой крупной рефакторизации!
- Не забывайте о preloading-стратегиях. Angular поддерживает автоматическую подгрузку ленивых модулей "на фоне" после старта приложения (PreloadAllModules), если вы хотите ускорить переходы без потери скорости старта.
6. Типичные ошибки и подводные камни
Ошибка №1: Неправильный путь к модулю или компоненту в loadChildren/loadComponent.
Если вы опечатались или переместили файл, Angular не найдёт модуль и выдаст ошибку при переходе по маршруту. Проверьте путь и экспортируемое имя класса.
Ошибка №2: Попытка лениво загрузить модуль без собственного роутинга.
Лениво загружаемый модуль должен иметь свой собственный RoutingModule или хотя бы массив маршрутов. Иначе при переходе по пути будет пусто.
Ошибка №3: Дублирование сервисов из-за разных инжекторов.
Если сервис предоставлен только в ленивом модуле (не в providedIn: 'root'), то экземпляры будут разные для каждого модуля. Это может привести к неожиданному поведению — например, разные "корзины" покупок в разных разделах.
Ошибка №4: Загрузка всего приложения в одном бандле.
Если вы случайно импортируете ленивый модуль в основной модуль (например, через imports: [AdminModule] в AppModule), он перестаёт быть ленивым! Проверяйте, что ленивые модули только в маршрутах.
Ошибка №5: Переиспользование компонентов между ленивыми и eagerly-загружаемыми модулями без shared-модуля/папки.
Это может привести к "дублирующимся" копиям компонента и ошибкам DI. Для переиспользования всегда выносите общий код в shared.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ