ноябрь 11, 2017 · angular

Общий функционал и Lazy-модули в Angular 5

Lazy-модули позволяют эффективно уменьшить размер "первой загрузки" и ускорить скорость старта приложения. Если вы еще не знакомы с ними, то основная идея в том, чтобы разбить приложение на модули, которые будут загружаться при переходе на определенный url.

За загрузку отвечает роутер и важным условием для их правильной работы является отсутствие прямого импорта между модулями, иначе код попадет в общий чанк.

Однако часто появляется необходимость использовать компоненты одного модуля в другом и тут есть несколько вариантов дальнейших действий.

Суть проблемы

Рассмотрим простое приложение-пример, это упрощенная структура для игрового видео-каталога. На данный момент с двумя Lazy-модулями и одним компонентом.

dia-1

Для примера используется Angular CLI со стандартными настройками.

Бандлы модулей GamesModule и VideosModule загружаются только при переходе по соотвествующим url: /games и /videos.

Теперь представим ситуацию в которой нам понадобился GamePanelComponent внутри VideosModule, например, чтобы показать на странице видео панельку игры. Если мы просто добавим импорт GamesModule в VideosModule, то результат компиляции будет таким:

dia-2

Общий функционал попадает в common.chunk.js и для этого не требуется каких-то дополнительных действий с вашей стороны. Общий чанк будет загружаться при переходе по /games и /videos, но не на главной странице.

Но в общий чанк будет попадать весь GamesModule, хотя мы хотели "расшарить" только GamePanelComponent. А при росте приложения и переплетении функционала все модули окажутся в нем.

Кроме этого при импорте lazy-модулей затягиваются и пути роутера, поэтому так делать в принципе не стоит.

Если же добавить GamePanelComponent в declarations обоих модулей, то получите ошибку: Error: Type GamePanelComponent is part of the declarations of 2 modules: GamesModule and VideosModule!

Как же правильно обмениваться функционалом между модулями, чтобы начальный бандл был минимального размера, а структура проекта простой и понятной?

Shared-модуль в корне

Один из самых простых и очевидных вариантов: добавить SharedModule, декларировать в нем общие компоненты и импортировать в нужных модулях.

dia-3

С таким подходом есть пару проблем. Обратите внимание, что GamePanelComponent оказался "оторван" от GamesModule и это не очень хорошо для последующей работы над приложением.

dia-3-1

Также, при активном росте приложения, SharedModule станен большим и неудобным для использования и сам потребует разделения на модули.

Попробуем немного изменить наш подход.

Вложенные shared-модули

Чтобы функционал не покидал родительский каталог, мы можем создать shared-модуль на подобии routing-модуля: GamesSharedModule.

dia-3-2

Так у каждого lazy-модуля будет свой shared-модуль с компонентами, которые он дает использовать по всему приложению.

games-shared.module.ts
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { GamePanelComponent } from './game-panel/game-panel.component';

@NgModule({
  imports: [
    CommonModule,
  ],
  declarations: [
    GamePanelComponent,
  ],
  exports: [
    GamePanelComponent,
  ],
})
export class GamesSharedModule {
}
games.module.ts
...
@NgModule({
  imports: [
    ...
    GamesSharedModule,
  ],
  ...
})
export class GamesModule {
}
videos.module.ts
...
@NgModule({
  imports: [
    ...
    GamesSharedModule,
  ],
  ...
})
export class VideosModule {
}

Так мы решили проблему разноса функционала, но осталась еще одна и она сильно себя проявляет на больших проектах. Внутри каталога вашего lazy-модуля трудно предсказать какой функционал в каком модуле задекларирован (в основном или shared), глядя только на структуру файлов.

До изучения содержимого файла shared-модуля, вы не можете сказать выносится ли компонент и можете ли вы его использовать в других модулях.

Микро-модули

При необходимости расшарить какой-то компонент, мы можем помещать его в свой микро-модуль. Я еще не встречал употребление этого термина в таком контексте, но он удачно описывает происходящее.

dia-4-1

Это даст несколько преимуществ:

game-panel.module.ts
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { GamePanelComponent } from './game-panel.component';

@NgModule({
  imports: [
    CommonModule,
  ],
  declarations: [
    GamePanelComponent,
  ],
  exports: [
    GamePanelComponent,
  ],
})
export class GamePanelModule {
}

Конечно же, в рамках таких микро-модулей может быть несколько компонентов и даже сервисы.

С точки зрения разделения на чанки ничего не поменяется, общий функционал будет в common.chunk.js.

dia-4

Но если в какой-то момент микро-модуль больше нет необходимости использовать в других модулях, то достаточно убрать его импорт. Когда код импортируется только в одном из лейзи-модулей, он перестает попадать в общий чанк.

dia-5

Исходный код приложения-примера: https://github.com/Navix/ng-micro-modules-example


Мы рассмотрели 3 варианта организации общего функционала, каждый следующий способ требует большего количества бойлерплейт кода.

С ростом вашего приложения, меняются и проблемы, которые необходимо решать. И мы можем видеть, что Angular дает нам инструменты для удобной работы с проектами любого размера.

  • LinkedIn
  • Tumblr
  • Reddit
  • Google+
  • Pinterest
  • Pocket