Главная » Статьи » Использование модуля Angular NgModules для многоразового кода и многое другое

Использование модуля Angular NgModules для многоразового кода и многое другое

Использование модуля Angular NgModules для многоразового кода и многое другое

От автора: в Angular модуль NgModules — основная концепция, которая является частью каждого приложения и помогает связать некоторые важные детали для компилятора и runtime. Они особенно полезны для организации кода в функциях, ленивых роутов загрузки и создания многоразовых библиотек.

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

Модули JavaScript не являются NgModules

Давайте сначала проясним, что такое модули JavaScript (иногда называемые модулями ES6). Это языковая конструкция, которая упрощает организацию вашего кода.

В основе модули Javascript являются файлами JavaScript, которые содержат либо ключевые слова import либо export , которые заставляют объекты, определенные внутри этого файла, быть закрытыми, если вы не экспортируете их. Я рекомендую вам просмотреть приведенную выше ссылку для более глубокого понимания, но, по сути, это способ организовать ваш код и легко поделиться им, не полагаясь на страшный глобальный охват.

Когда вы создаете Angular приложение с TypeScript, каждое использование import или export в исходном коде считается модулем JavaScript. TypeScript способен обрабатывать загрузку модуля для вас.

Примечание. Чтобы все было ясно в этой статье, я всегда буду ссылаться на модули JavaScript и NgModules по их полному имени.

Основной NgModule, AppModule

Давайте начнем с изучения базового NgModule, который существует в каждом приложении Angular, AppModule (который генерируется по умолчанию в любом новом Angular приложении). Это похоже на:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule ], providers: [], bootstrap: [AppComponent]
})
export class AppModule { }

Angular использует декораторы для определения метаданных, которые необходимо знать во время компиляции. Чтобы определить NgModue, вы просто добавляете декоратор @NgModule() над классом. Класс не всегда может быть пустым, но часто это так. Однако вам нужно будет определить объект с некоторыми свойствами для NgModule, чтобы что-либо сделать.

Когда приложение загружается, ему должен быть предоставлен NgModule для создания экземпляра. Если вы посмотрите в основной файл приложения (также обычно называемый main.ts), вы увидите platformBrowserDynamic().bootstrapModule(AppModule), то есть как приложение регистрирует и запускает AppModule (который можно назвать как угодно, но почти всегда называют так).

Свойства NgModule

На странице документации API NgModule перечислены свойства, которые вы можете передать при определении NgModule, но мы также рассмотрим их здесь. Они все необязательны, но вам нужно будет определить значения, по крайней мере, для одного из них, чтобы NgModule мог что-то сделать.

Свойства NgModule

На странице документации API NgModule перечислены свойства, которые вы можете передать при определении NgModule, но мы также рассмотрим их здесь. Они все необязательны, но вам нужно будет определить значения, по крайней мере, для одного из них, чтобы NgModule мог что-то сделать.

providers

providers — это массив, содержащий список любых провайдеров (инъекционных сервисов), доступных для этого NgModule. У провайдеров есть область видимости, и если они перечислены в ленивом загруженном NgModule, они недоступны вне этого NgModule.

declarations

Массив declarations должен содержать список любых директив, компонентов или пайпов, которые определяет этот NgModule. Это позволяет компилятору найти эти элементы и обеспечить их правильное соединение. Если это корневой NgModule, тогда объявления доступны для всех NgModules. В противном случае они видны только одному и тому же NgModule.

imports

Если ваш NgModule зависит от любых других объектов от другого NgModule, вам придется добавить его в массив imports. Это гарантирует, что система вставки зависимостей и компилятор знают об импортированных элементах.

exports

Используя массив exports, вы можете определить, какие директивы, компоненты и пайпы доступны для любого NgModule, который импортирует этот NgModule. Например, в библиотеке пользовательского интерфейса вы экспортируете все компоненты, составляющие библиотеку.

entryComponents

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

bootstrap

Вы можете определить любое количество компонентов для начальной загрузки, когда приложение будет загружено первым. Обычно нужно только загрузить основной корневой компонент (обычно называемый AppComponent ), но если у вас было более одного корневого компонента, каждый из них будет объявлен здесь. Добавляя компонент к массиву bootstrap , он также добавляется в список entryComponents и предварительно скомпилирован.

schemas

Схемы — это способ определить, как Angular компилирует шаблоны, и выкинет ли он ошибку, когда найдет элементы, которые не являются стандартными HTML или известными компонентами. По умолчанию Angular выдает ошибку, когда находит элемент в шаблоне, который он не знает, но вы можете изменить это поведение, установив схему либо NO_ERRORS_SCHEMA (чтобы разрешить все элементы и свойства), либо CUSTOM_ELEMENTS_SCHEMA (чтобы разрешить любые элементы или свойства с – в их имени).

id

Это свойство позволяет вам предоставить NgModule уникальный идентификатор, который вы можете использовать для получения заводской ссылки модуля. Редко используется.

Примеры NgModule

Чтобы проиллюстрировать, как NgModule используется с Angular, давайте рассмотрим набор примеров, которые показывают, как легко обращаться с различными вариантами использования.

Особенности NgModules

Самый простой пример использования NgModules, помимо AppModule — для Feature NgModules (обычно называемых функциональными модулями, но они не позволяют согласовать термины). Они помогают разделить отдельные части вашего приложения и настоятельно рекомендуются. В большинстве случаев они такие же, как и основной App NgModule. Давайте рассмотрим базовую функцию NgModule:

@NgModule({ declarations: [ ForumComponent, ForumsComponent, ThreadComponent, ThreadsComponent ], imports: [ CommonModule, FormsModule, ], exports: [ ForumsComponent ] providers: [ ForumsService ]
})
export class ForumsModule { }

Этот простой Feature NgModule определяет четыре компонента, один провадйер и импортирует два модуля, которые требуются компонентами и сервисом. Вместе они включают необходимые фрагменты для раздела форумов приложения.

Элементы providers доступны для любого NgModule, который импортирует ForumsModule для ForumsModule , но важно понимать, что каждый NgModule получит свой собственный экземпляр этой службы. Это отличается от провайдеров, перечисленных в корневом модуле NgModule, из которого вы всегда будете получать один и тот же экземпляр (если он не предоставляется повторно). Здесь важно понимать инъекцию зависимостей, особенно иерархическую инъекцию зависимостей. Легко думать, что вы получите тот же экземпляр службы и измените свойства на нем, но никогда не увидите изменений в другом месте приложения.

Как мы узнали ранее, элементы в declarations фактически не доступны для использования в других NgModules, поскольку они являются частными для этого NgModule. Чтобы исправить это, вы можете экспортировать те декларации, которые хотите использовать в других NgModules, например, в этом фрагменте, где он экспортирует только ForumsComponent. Теперь в любых других функциях NgModules вы можете поместить <app-forums></app-forums> forums <app-forums></app-forums> (или что бы то ни было селектором для компонента) для отображения ForumsComponent в шаблоне.

Другим ключевым отличием является то, что ForumsModule импортирует CommonModule вместо BrowserModule. Модуль BrowserModule должен быть импортирован только в корневой NgModule, но CommonModule содержит основные Angular директивы и пайпы (такие как NgFor и пайп Date). Если ваш Feature NgModule не использует ни одну из этих функций, на самом деле ему не нужен CommonModule.

Теперь, когда вы хотите использовать ForumsModule в своем проекте, вам нужно импортировать его в свой AppModule как вы видите здесь:

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

Этот NgModule затем импортируется в основной AppModule для его правильной загрузки, который включает элементы в ForumsModule поставщиков ForumsModule и любые экспортированные элементы для потребления в вашем приложении.
Когда вы используете Angular CLI, вы можете легко генерировать функции NgModules, запустив генератор для нового NgModule:

ng generate module path/to/module/feature 

Вы можете организовать свои функции NgModules любым способом, который вам подходит, но общая рекомендация — группировать похожие вещи, которые используются на одном и том же представлении. Я пытаюсь сделать небольшое количество функций NgModules для хранения общедоступных вещей, а затем больше сосредоточиться на NgModules для каждой основной функции приложения.

Ленивая Загрузка NgModules с роутами

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

Лучший способ начать — создать Feature NgModule для уникальных частей маршрута. Возможно, вам захочется сгруппировать более одного роута, если они почти всегда используются вместе. Например, если у вас есть страница учетной записи клиента с несколькими подстраницами для управления реквизитами учетной записи, более чем вероятно, что вы объявите их как часть одного и того же NgModule.

Нет никакой разницы в том, как вы определяете сам NgModule, за исключением того, что вам нужно определить некоторые маршруты с помощью RouterModule.forChild() . У вас должен быть один маршрут с пустым путём, который будет действовать как корневой маршрут для этого Feature NgModule, и все остальные маршруты зависают от него:

@NgModule({ declarations: [ ForumComponent, ForumsComponent, ], imports: [ CommonModule, FormsModule, RouterModule.forChild([ {path: '', component: ForumsComponent}, {path: ':forum_id', component: ForumComponent} ]) ], providers: [ ForumsService ]
})
export class ForumsModule { }

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

Чтобы загрузить ленивый роут, основной AppModule определяет путь к этому Feature NgModule. Для этого вам нужно будет обновить конфигурацию корневого маршрутизатора для нового маршрута. В этом примере показано, как определить ленивый загруженный роут, задав ему свойства path и loadChildren:

const routes: Routes = [ { path: 'forums', loadChildren: 'app/forums/forums.module#ForumsModule' }, { path: '', component: HomeComponent }
];

Синтаксис свойства loadChildren — это строка, которая имеет путь к файлу NgModule (без расширения файла), символ #, а затем имя класса loadChildren: ‘path/to/module#ModuleName. Angular использует это, чтобы знать, где загрузить файл во время выполнения, и узнать имя NgModule.

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

Например, если вы посетите / маршрут в этом приложении, он загрузит ForumsModule и ForumsModule не будет загружен. Однако, как только пользователь нажимает ссылку для просмотра форумов, он замечает, что для пути / ForumsModule требуется, чтобы ForumsModule загружался, загружал его и регистрировал определенные маршруты из него.

Маршрутизация NgModules

Обычным шаблоном для Angular является использование отдельного NgModule для размещения всех ваших маршрутов. Это делается для разделения проблем и полностью необязательно. Angular CLI поддерживает автоматическое создание Routing NgModule при создании нового модуля, передавая флаг —routing:

ng generate module path/to/module/feature --routing 

Случается, что вы создаете автономный NgModule, который определяет ваши маршруты, а затем ваш Feature NgModule импортирует его. Вот что может выглядеть NgModule маршрутизации:

const routes: Routes = [ { path: '', component: ForumsComponent }
]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule]
})
export class ForumsRoutingModule { }

Затем вы просто импортируете его на свой ForumsModule как здесь:

@NgModule({ declarations: [ ForumComponent, ForumsComponent, ], imports: [ CommonModule, FormsModule, ForumsRoutingModule, ], providers: [ ForumsService ]
})
export class ForumsModule { }

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

Сервисы SingleTon

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

На самом деле, Angular маршрутизатор является хорошим примером этого. Когда вы определяете маршрут в корневом NgModule, вы используете RouterModule.forRoot(routes) , но внутри Feature NgModules вы используете RouterModule.forChild(routes) . Этот шаблон является общим для любой библиотеки многократного использования, которая нуждается в одном экземпляре службы (singleton). Мы можем сделать то же самое с любым NgModule, добавив два статических метода в наш NgModule, как вы видите здесь:

@NgModule({ declarations: [ ForumComponent, ForumsComponent, ThreadComponent, ThreadsComponent ], imports: [ CommonModule, FormsModule, ], exports: [ ForumsComponent ]
})
export class ForumsModule { static forRoot(): ModuleWithProviders { return { ngModule: ForumsModule, providers: [ForumsService] }; } static forChild(): ModuleWithProviders { return { ngModule: ForumsModule, providers: [] }; }
}

Затем в нашем AppModule вы определяете импорт с помощью forRoot(), который вернет NgModule с поставщиками. В любом другом модуле NgModule, который импортирует ForumsModule, необходимо использовать метод forChild(), чтобы повторно не объявлять провайдер (таким образом, создавая новый экземпляр):

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

NgModules для группировки NgModules

Вы можете объединить ряд других NgModules в один, чтобы упростить импорт и повторное использование. Например, в проекте Clarity, над которым я работаю, у нас есть ряд NgModules, которые экспортируют только другие NgModules. Например, это основной ClarityModule который фактически реэкспортирует другие отдельные NgModules, которые содержат каждый из компонентов:

@NgModule({ exports: [ ClrEmphasisModule, ClrDataModule, ClrIconModule, ClrModalModule, ClrLoadingModule, ClrIfExpandModule, ClrConditionalModule, ClrFocusTrapModule, ClrButtonModule, ClrCodeModule, ClrFormsModule, ClrLayoutModule, ClrPopoverModule, ClrWizardModule ]
})
export class ClarityModule { }

Это позволяет легко импортировать сразу несколько NgModules, но компилятору сложно узнать, какие NgModules используются или нет для оптимизации встряхивания дерева.

Заключение

Мы прошли по NgModules в Angular и рассмотрели ключевые варианты использования. Angular документация о NgModules также достаточно подробна, и если вы застряли, я предлагаю обратиться к FAQ.

Автор: Jeremy Wilken

Источник: https://www.sitepoint.com/

Редакция: Команда webformyself.