Меня зовут Джеффри Хаузер, разработчик группы разработки контента Disney Streaming Services. Моя команда создает порталы контента, которые позволяют редакторам управлять контентом для потребителей в видеосервисах, которые мы поддерживаем.

У каждой службы есть свой уникальный набор проблем и сложностей, которые нам приходилось решать. Сегодня я расскажу вам о прототипе, который мы недавно создали в качестве эксперимента, чтобы позволить нескольким системам подключаться к единому информационному порталу. Задача заключалась в создании отдельных приложений пользовательского интерфейса, которые можно было бы разрабатывать и развертывать независимо друг от друга, но при этом их можно было бы объединить в единый модуль для развертывания. Вот как мы это сделали.

Настройте рабочее пространство Angular

Вам потребуется установленный Angular CLI. Такая установка выходит за рамки данной статьи. Иди сюда и следуй инструкциям.

Оттуда создайте рабочее пространство Angular:

ng new my-app --routing

Вы увидите что-то вроде этого:

Это создаст стандартную архитектуру приложения Angular, которая включает node_modules, компонент по умолчанию, модуль маршрутизации и некоторые другие файлы, специфичные для Angular. Убедимся, что все настроено правильно. Перейдите в каталог my-app и запустите приложение:

cd my-app
ng serve

Вы увидите что-то вроде этого:

Укажите в своем любимом браузере localhost: 4200, чтобы увидеть, как работает приложение:

Вы готовы перейти к следующему шагу.

Создайте свои вспомогательные приложения

Когда я создавал начальный прототип, я использовал Nrwl Extensions для Angular, потому что у них была функция поддержки нескольких приложений в одной рабочей области. Однако функциональность нескольких приложений теперь встроена непосредственно в интерфейс командной строки Angular, поэтому в этом посте мы сосредоточимся на этом. Вместо использования ng new для создания рабочей области мы хотим использовать ng generate application. Это создаст новое приложение, расположенное рядом с приложением по умолчанию:

ng generate application app1 –-routing

Вы увидите что-то вроде этого:

Пока мы это делаем, давайте создадим третье приложение:

ng generate application app2 –-routing

Вы должны увидеть в консоли нечто похожее на предыдущий снимок экрана.

Взгляните на список каталогов:

Вероятно, это похоже на то, что вы ожидаете от проекта Angular:

  • e2e: для сквозных тестов
  • node_modules: все пакеты, которые необходимо было установить для запуска приложения.
  • src: источник основного приложения.
  • src / app: основной код приложения.
  • src / assets: папка с ресурсами для основного приложения.
  • src / environments: конфигурации среды.

Если вы присмотритесь, то заметите новый каталог с названием projects. Здесь находятся все вспомогательные приложения. Разверните его:

В каталог проектов входят:

  • app1: исходный код вашего первого настраиваемого дополнительного приложения.
  • app1 / app: исходный код для app1.
  • app1 / assets ресурсы для app1.
  • приложение1 / среды: конфигурация среды для приложения1.
  • app1-e2e: сквозное тестирование для app1.
  • app2: исходный код для вашего второго настраиваемого дополнительного приложения.
  • app2 / app: исходный код для app2.
  • app2 / assets ресурсы для app2.
  • приложение2 / среды: конфигурация среды для приложения2.

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

Давайте откроем projects / app1 / src / app / app.component.ts. Измените класс AppComponent так, чтобы в заголовке было указано приложение 1, например:

export class AppComponent {
 title = ‘app1’;
}

Затем откройте тот же файл во втором дополнительном приложении по адресу projects / app2 / src / app / app.component.ts:

export class AppComponent {
  title = 'app2';
}

Для целей нашего тестирования эти изменения покажут вам, какое приложение вы используете. Чтобы запустить одно из вспомогательных приложений, вы просто используете аргумент проекта для ng serve, например:

ng serve –-project app1

Вы увидите это в консоли:

Это, вероятно, скучно, потому что вы уже видели это при запуске основного приложения. Загрузите браузер на localhost: 4200, и вы увидите следующее:

Это компонент приложения Angular по умолчанию, но в нашем заголовке указано, что мы запускаем код app1. Вы можете запустить второй подпроект следующим образом:

ng serve –-project=app2

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

ng serve

Поскольку все приложения используют один и тот же порт, вы не можете запускать их одновременно, но это подходило для наших первоначальных целей.

Внедрение вспомогательных приложений в основное приложение

Вы создали новое рабочее пространство с основным приложением и двумя вспомогательными приложениями, так как же их объединить? Вы, наверное, знаете, что вы можете вставить один модуль Angular внутрь другого с помощью команды import. Фактически, это использование модуля маршрутизации по умолчанию. Откройте app.module.ts из любого из наших проектов:

imports: [
  BrowserModule,
  AppRoutingModule
],

Сюда вносятся два отдельных модуля. Вот как мы хотим внедрить всю функциональность из наших субприложений в основные приложения. Однако есть второстепенная проблема. Все классы @NgModule по умолчанию называются AppModule. Это просто сбивает нас с толку, и чего мы хотим, я хотел избежать.

Давайте посмотрим на метаданные @NgModule для app1:

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

В этом же файле я собираюсь добавить еще один класс, App1SharedModule:

@NgModule({})
export class App1SharedModule{
  static forRoot(): ModuleWithProviders {
    return {
      ngModule: AppModule,
      providers: []
    }
  }
}

Этот класс содержит метаданные @NgModule, но без информации. Класс использует соглашение forRoot (), которое возвращает экземпляр ModuleWithProviders. Возвращенный @NgModule относится к исходному классу AppModule и метаданным. Значение провайдеров - это пустой массив.

В этом примере мы сохраним значение provider как пустой массив, но в реальном приложении у нас обязательно будут несколько поставщиков, поэтому давайте создадим отдельную константу для их хранения:

const providers = []

Когда мы добавляем провайдеров в приложение, мы добавляем их в этот массив. Метаданные @NgModule AppModule должны ссылаться на это:

providers: providers,

Как и метод forRoot () в App1SharedModule:

providers: providers

Это одно и то же использование в обоих местах. Это все, что нам нужно сделать для app1. Вы можете повторить это для app2.

Теперь откройте src / app / app.module.ts. Мы можем просто импортировать два новых класса модулей, которые мы только что создали. В метаданных @ngModule:

imports: [
  BrowserModule,
  AppRoutingModule,
  App1SharedModule.forRoot(),
  App2SharedModule.forRoot()
],

С подмодулями, добавленными в наше основное приложение, мы получаем возможность использовать все поставщики, компоненты, объявления и импортированные из вспомогательного приложения в основном приложении, но при этом можем запускать вспомогательные приложения независимо от основного. Давайте посмотрим на операторы импорта:

import {App1SharedModule} 
       from "../../projects/app1/src/app/app.module";
import {App2SharedModule} 
       from "../../projects/app2/src/app/app.module";

Ничего особенного или удивительного здесь нет, он просто просматривает дерево каталогов в поисках app.module. Кажется немного странным выходить из основного каталога приложения в поисках импорта, но это вполне допустимо.

Обмен маршрутами

Остался один «орешек»! Как мы делимся маршрутами из субприложений с основными приложениями? Конечно, это важная часть встраивания одного приложения в другое, верно? Я нашел путь. Во-первых, нам нужно создать несколько маршрутов в обоих субприложениях.

Во-первых, давайте настроим каждое приложение с некоторыми маршрутами и компонентами. Сначала создайте компонент в app1:

ng generate component view1--project=app1

Вы увидите что-то вроде этого:

Давайте создадим еще один компонент представления и компонент навигации в app1:

ng generate component view2 —-project=app1
ng generate component nav --project=app1

Откройте view1.component.html и добавьте в навигацию. Теперь это будет выглядеть так:

<app-nav></app-nav>
<p>
  app1 view1 works!
</p>

Сделайте то же самое для view2.component.html:

<app-nav></app-nav>
<p>
  app1 view2 works!
</p>

В обоих случаях я сделал одну модификацию текста по умолчанию в компоненте. В этом примере мы не собираемся добавлять в компонент функциональные возможности, помимо отображения по умолчанию.

Откройте файл nav.component.html и добавьте ссылки для перехода между двумя маршрутами:

<a routerLink="/app1/one" >One</a>  | 
<a routerLink="/app1/two" >Two</a>

Наконец, добавим маршруты. Откройте модуль маршрутизации приложения и найдите константу routes:

const routes: Routes = [];

По умолчанию маршруты не определены. Добавьте эти три:

const routes: Routes = [
 { path: 'app1/one', component: View1Component },
 { path: 'app1/two', component: View2Component },
 { path: 'app1', redirectTo: 'app1/one' }
];

Я настроил маршрут для каждого компонента, app1 / one и app1 / two. Я также настроил маршрут для app1, который перенаправлял на первый экран. Обратите внимание, здесь я не настраивал маршрут перехвата всех, например:

{ path: '**', redirectTo: '/app1' }

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

Запустите приложение и загрузите его в браузере. Загрузите его с помощью localhost: 4200 / app1, и вы сможете перемещаться между двумя компонентами:

Щелкните ссылку "два":

Теперь, когда это работает. Теперь выполните те же действия для настройки app2. Сначала сгенерируйте компоненты:

ng generate component view1 —-project=app2
ng generate component view2 —-project=app2
ng generate component nav —-project=app2

Это изменения в view1.component.html:

<app-nav></app-nav>
<p>
  app2 view1 works!
</p>

view2.component.html выглядит так:

<app-nav></app-nav>
<p>
  app2 view2 works!
</p>

Единственное отличие от app1 - это изменение текста в основном теле, с app1 на app2. В реальном приложении я бы ожидал, что такие суб-приложения будут иметь существенные различия в функциональности, маршрутах и ​​экранах, но в этом примере я сохраняю их простоту.

Измените компонент навигации:

<a routerLink="/app2/one" >One</a>  | 
<a routerLink="/app2/two" >Two</a>

App2 поместит все страницы в маршрут app2. Откройте модуль маршрутизации app2s:

const routes: Routes = [
  { path: 'app2/one', component: View1Component },
  { path: 'app2/two', component: View2Component },
  { path: 'app2', redirectTo: 'app2/one' },
];

Запустите приложение:

ng serve -–project=app2

Загрузите его на localhost: 4200 / app2. Вы увидите первую загрузку просмотра:

Щелкните ссылку «два»:

Теперь у нас есть оба наших подмодуля, пришло время настроить основное приложение для использования этих приложений и маршрутов.

Откройте модуль маршрутизации приложения и добавьте маршруты:

const routes: Routes = [
 {path: 'app1', 
  loadChildren: '../../projects/app1/src/app/app.module#App1SharedModule'},
 {path: 'app2', 
  loadChildren: '../../projects/app2/src/app/app.module#App2SharedModule'},
 { path: '**', redirectTo: '/app1/one'}
];

Вместо того, чтобы указывать конкретный маршрут на конкретный компонент, мы используем свойство loadChildren для загрузки маршрутов из подмодулей по запросу. Это используется как часть механизма ленивой загрузки модулей в Angular. Значение loadChildren указывает на главное приложение, которое загружает модули. Я установил маршрут для ленивых загружаемых модулей по имени модуля, app1 или app2. Это отражает базовый маршрут, используемый в субмодуле. Стандартный маршрут перенаправления по умолчанию направляет пользователей в app1 / one.

Внутри AppRoutingModule вам нужно будет добавить импорт для обоих упомянутых модулей:

imports: [
  RouterModule.forRoot(routes),
  App1SharedModule.forRoot(),
  App2SharedModule.forRoot()
],

RouterModule.forRoot () уже был там, но App1SharedModule.forRoot () и App2SharedModule.forRoot () - новые. Этот модуль маршрутизатора Angular должен знать об этих других маршрутах, чтобы поддерживать ленивую загрузку.

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

ng generate component nav

Вы увидите это:

Добавьте навигацию в свой app.component.html:

<app-nav></app-nav>
<br/><br>
<router-outlet></router-outlet>

И добавьте ссылки между двумя субприложениями в навигаторе:

<a routerLink="/app1" >App 1</a> | 
<a routerLink="/app2" >App 2</a>

Теперь тебе должно быть хорошо. Запустить:

ng serve

Загрузите ваш браузер по адресу locahost: 4200:

Щелкните ссылки, и вы увидите, что маршруты правильно меняются:

Вы даже можете переключиться на другое приложение:

И переключаться между видами там:

Последние мысли

Создавая этот прототип, мне было очень весело, и я вижу преимущества создания приложения с помощью этого метода. Это помогает разделить логику и снизить когнитивную нагрузку при разработке приложения. Умение сосредотачиваться важно для продуктивности любого программиста. Хотя мы решили не использовать этот подход, он дал некоторые полезные советы по пути.