листопад 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