Меня зовут Джеффри Хаузер, разработчик группы разработки контента 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:
Щелкните ссылки, и вы увидите, что маршруты правильно меняются:
Вы даже можете переключиться на другое приложение:
И переключаться между видами там:
Последние мысли
Создавая этот прототип, мне было очень весело, и я вижу преимущества создания приложения с помощью этого метода. Это помогает разделить логику и снизить когнитивную нагрузку при разработке приложения. Умение сосредотачиваться важно для продуктивности любого программиста. Хотя мы решили не использовать этот подход, он дал некоторые полезные советы по пути.