Модули NgModules
Определяйте модули приложения с помощью @NgModule.
Модули NgModules помогают организовать приложение в связные блоки функциональности.
Модуль NgModule — это класс, снабженный декоратором функции @NgModule. @NgModule принимает объект метаданных, который сообщает Angular, как компилировать и запускать код модуля. Он идентифицирует собственные компоненты, директивы и трубы модуля, делая некоторые из них общедоступными, чтобы внешние компоненты могли их использовать. @NgModule может добавлять поставщики сервисов в инжекторы зависимостей приложения. И здесь охвачено много других вариантов.
Перед чтением этой страницы ознакомьтесь со страницей Корневой модуль, которая вводит модули NgModules и основные принципы создания и обслуживания единственного корневого AppModule для всего приложения.
Эта страница описывает модули NgModules более подробно.
Содержание
- Модульность Angular
- Модуль корня приложения
- Запуск модуля корня
- Объявления
- Поставщики
- Импорты
- Разрешение конфликтов
- Модули функций
- Лениво загружаемые модули с маршрутизатором
- Модули общих компонентов
- Модуль ядра
- Настройка основных сервисов с forRoot
- Предотвратить повторный импорт CoreModule
- Свойства метаданных NgModule
Примеры
Эта страница поясняет модули NgModules с помощью последовательности улучшений примера на тему «Путешествие по героям». Вот индекс примеров в реальном времени в ключевые моменты эволюции примера:
- Приложениe NgModule минимального размера
- Первый модуль контактов
- Переработанный модуль контактов
- Непосредственно перед добавлением SharedModule
- Окончательная версия
Часто задаваемые вопросы (FAQ)
Эта страница описывает концепции NgModule в формате учебного пособия.
В справочнике Вопросы и ответы по NgModule приводятся ответы на конкретные вопросы о проектировании и реализации. Прочитайте эту страницу перед чтением этих вопросов и ответов.
Модульность Angular
Модули — отличный способ организовать приложение и расширить его возможностями внешних библиотек.
Многие библиотеки Angular представляют собой модули (например, FormsModule, HttpModule, и RouterModule). Многие сторонние библиотеки доступны как модули NgModules (например, Material Design, Ionic, AngularFire2).
Модули NgModules объединяют компоненты, директивы и трубы в связные блоки функциональности, каждый из которых сфокусирован на определённой области функциональности, предметной области приложения, рабочем процессе или общем наборе утилит.
Модули также могут добавлять сервисы в приложение. Такие сервисы могут быть разработаны внутри, например, логирования приложения. Сервисы могут поступать из внешних источников, например, маршрутизатор Angular и Http-клиент.
Модули могут загружаться немедленно при запуске приложения. Они также могут загружаться асинхронно с помощью маршрутизатора как лениво загружаемые.
Модуль NgModule — это класс, снабжённый метаданными @NgModule. Метаданные выполняют следующие действия:
- Объявляют, какие компоненты, директивы и трубы принадлежат модулю.
- Делают некоторые из этих классов общедоступными, чтобы другие шаблоны компонентов могли их использовать.
- Импортируют другие модули с необходимыми компонентами, директивами и трубами для компонентов в этом модуле.
- Предоставляют сервисы на уровне приложения, которые может использовать любой компонент приложения.
В каждом приложении Angular есть по крайней мере один класс модуля, корневой модуль. Вы запускаете приложение, используя этот модуль.
Корневой модуль — всё, что вам нужно в простом приложении с несколькими компонентами. По мере роста приложения вы перерабатываете корневой модуль в модули функций, которые представляют собой наборы связанной функциональности. Затем вы импортируете эти модули в корневой модуль.
Позже в этой странице вы прочитаете об этом процессе. Сейчас вы начнете с корневого модуля.
AppModule: корневой модуль приложения
В каждом приложении Angular есть класс корневого модуля. По соглашению, корневой модуль называется AppModule и находится в файле с именем app.module.ts.
AppModule из шаблона QuickStart на странице Настройка является максимально минимальным:
src/app/app.module.ts (минимальный)
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
imports: [ BrowserModule ],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
Декоратор @NgModule определяет метаданные для модуля. Эта страница использует интуитивный подход к пониманию метаданных и заполняет детали по мере продвижения.
Метаданные импортируют один вспомогательный модуль, BrowserModule, который должен импортировать каждый браузерный компонент.
BrowserModule регистрирует важные поставщики сервисов приложения. Он также включает общие директивы, такие как NgIf и NgFor, которые сразу же становятся видимыми и доступными в шаблонах компонентов этого модуля.
Список declarations идентифицирует единственный компонент приложения, корневой компонент, верхнюю часть довольно простого дерева компонентов приложения.
В примере AppComponent просто отображается привязанный к данным заголовок:
src/app/app.component.ts (минимальный)
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: '<h1>{{title}}</h1>',
})
export class AppComponent {
title = 'Minimal NgModule';
}
Наконец, свойство @NgModule.bootstrap идентифицирует этот AppComponent как компонент запуска. При запуске приложения Angular размещает HTML-отображение AppComponent в DOM, внутри тегов элемента <my-app> в index.html.
Загрузка в main.ts
Вы запускаете приложение, загружая AppModule в файле main.ts.
Angular предлагает различные варианты загрузки, нацеленные на несколько платформ. Эта страница описывает два варианта, оба ориентированных на браузер.
Динамическая загрузка с компилятором just-in-time (JIT)
В первом, динамическом варианте, компилятор Angular компилирует приложение в браузере, а затем запускает приложение.
src/main.ts (динамический)
// The browser platform with a compiler
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
// The app module
import { AppModule } from './app/app.module';
// Compile and launch the module
platformBrowserDynamic().bootstrapModule(AppModule);
Примеры на этой странице демонстрируют подход динамической загрузки.
Попробуйте пример в реальном времени.
Статическая загрузка с компилятором ahead-of-time (AOT)
Рассмотрите статический вариант, который может создать значительно меньшее приложение, запускаемое быстрее, особенно на мобильных устройствах и сетях с высокой задержкой.
В статическом варианте компилятор Angular выполняется заранее в процессе сборки, создавая набор фабрик классов в своих собственных файлах. Среди них — AppModuleNgFactory.
Синтаксис для загрузки предварительно скомпилированного AppModuleNgFactory аналогичен динамическому варианту, который загружает класс AppModule.
src/main.ts (статический)
// The browser platform without a compiler
import { platformBrowser } from '@angular/platform-browser';
// The app module factory produced by the static offline compiler
import { AppModuleNgFactory } from './app/app.module.ngfactory';
// Launch with the app module factory.
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);
Поскольку всё приложение было предварительно скомпилировано, Angular не отправляет компилятор Angular в браузер и не компилирует его в браузере.
Код приложения, загруженный в браузер, намного меньше, чем динамический эквивалент, и он готов к немедленному выполнению. Ускорение работы может быть значительным.
Как компилятор JIT, так и компилятор AOT генерируют класс AppModuleNgFactory из одного и того же исходного кода AppModule. JIT-компилятор создаёт этот класс фабрики на лету, в памяти, в браузере. AOT-компилятор выводит фабрику в физический файл, который импортируется здесь в статическом варианте main.ts.
В общем случае, AppModule не должен знать и не должен заботиться о том, как он загружается.
Хотя AppModule развивается по мере роста приложения, код загрузки в main.ts не меняется. Это последний раз, когда вы будете изучать main.ts.
Объявление директив и компонентов
По мере развития приложения первое дополнение — это HighlightDirective, директива атрибута, которая устанавливает цвет фона прикреплённого элемента.
src/app/highlight.directive.ts
import { Directive, ElementRef } from '@angular/core';
@Directive({ selector: '[highlight]' })
/** Highlight the attached element in gold */
export class HighlightDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = 'gold';
console.log(
`* AppRoot highlight called for ${el.nativeElement.tagName}`);
}
}
Обновите шаблон AppComponent для присоединения директивы к заголовку:
template: '<h1 highlight>{{title}}</h1>'
Если вы сейчас запустите приложение, Angular не распознает атрибут highlight и проигнорирует его. Вам необходимо объявить директиву в AppModule.
Импортируйте класс HighlightDirective и добавьте его в declarations модуля следующим образом:
declarations: [ AppComponent, HighlightDirective, ],
Добавить компонент
Переработайте заголовок в свой собственный TitleComponent. Шаблон компонента связывает свойства компонента title и subtitle следующим образом:
src/app/title.component.html
<h1 highlight>{{title}} {{subtitle}}</h1>
src/app/title.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-title',
templateUrl: './title.component.html',
})
export class TitleComponent {
@Input() subtitle = '';
title = 'Angular Modules';
}
Перепишите AppComponent для отображения нового TitleComponent в элементе <app-title>, используя привязку входных данных для установки subtitle.
src/app/app.component.ts (v1)
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: '<app-title [subtitle]="subtitle"></app-title>'
})
export class AppComponent {
subtitle = '(v1)';
}
Angular не распознает тег <app-title> до тех пор, пока вы не объявите его в AppModule. Импортируйте класс TitleComponent и добавьте его в declarations модуля:
declarations: [
AppComponent,
HighlightDirective,
TitleComponent,
],
Поставщики сервисов
Модули — отличный способ предоставления сервисов для всех компонентов модуля.
На странице Внедрение зависимостей описана иерархическая система внедрения зависимостей Angular и способ её настройки с помощью поставщиков на различных уровнях древа компонентов приложения.
Модуль может добавлять поставщиков в корневой инжектор зависимостей приложения, делая эти сервисы доступными во всём приложении.
Многие приложения собирают информацию о текущем авторизованном пользователе и делают её доступной через сервис пользователя. В этом примере есть имитация такого UserService.
src/app/user.service.ts
import { Injectable } from '@angular/core';
@Injectable()
/** Dummy version of an authenticated user service */
export class UserService {
userName = 'Sherlock Holmes';
}
В примере приложение должно отображать приветствие авторизованному пользователю сразу под заголовком приложения. Обновите шаблон TitleComponent для отображения приветствия под заголовком приложения.
src/app/title.component.html
<h1 highlight>{{title}} {{subtitle}}</h1>
<p *ngIf="user">
<i>Welcome, {{user}}</i>
<p>
Обновите класс TitleComponent с конструктором, который вводит в него UserService и устанавливает свойство компонента user из сервиса.
src/app/title.component.ts
import { Component, Input } from '@angular/core';
import { UserService } from './user.service';
@Component({
selector: 'app-title',
templateUrl: './title.component.html',
})
export class TitleComponent {
@Input() subtitle = '';
title = 'Angular Modules';
user = '';
constructor(userService: UserService) {
this.user = userService.userName;
}
}
Вы определили и использовали сервис. Теперь, чтобы предоставить его всем компонентам, добавьте его в свойство providers в метаданные AppModule:
src/app/app.module.ts (providers)
providers: [ UserService ],
Импорт поддерживающих модулей
В переработанном TitleComponent, директива *ngIf защищает сообщение. Сообщение не отображается, если пользователь отсутствует.
src/app/title.component.html (ngIf)
<p *ngIf="user">
<i>Welcome, {{user}}</i>
<p>
Хотя AppModule не объявляет NgIf, приложение всё равно компилируется и работает. Как это возможно? Компилятор Angular должен либо игнорировать, либо жаловаться на нераспознанный HTML.
Angular распознаёт NgIf, так как вы импортировали его ранее. Первоначальная версия AppModule импортирует BrowserModule.
src/app/app.module.ts (imports)
imports: [ BrowserModule ],
Импорт BrowserModule сделал все его публичные компоненты, директивы и трубы видимыми для шаблонов компонентов в AppModule.
Точнее,
NgIfобъявлен вCommonModuleиз@angular/common.
CommonModuleпредоставляет многие общие директивы, необходимые приложениям, включаяngIfиngFor.
BrowserModuleимпортируетCommonModuleи переэкспортирует его. В итоге, импортирующийBrowserModuleмодуль автоматически получает директивыCommonModule.
Многие известные директивы Angular не принадлежат CommonModule. Например, NgModel и RouterLink принадлежат модулям Angular FormsModule и RouterModule соответственно. Вам необходимо импортировать эти модули, прежде чем вы сможете использовать их директивы.
Чтобы проиллюстрировать этот момент, вы расширите приложение с ContactComponent, компонентом формы, который импортирует поддержку форм из модуля Angular FormsModule.
Добавить компонент ContactComponent
Формы Angular — отличный способ управления вводом данных пользователем.
Компонент ContactComponent представляет собой "редактор контактов", реализованный с помощью форм Angular в стиле форм с управлением через шаблон.
Стили форм Angular
Вы можете писать компоненты форм Angular в стиле с управлением через шаблон или реактивном стиле.
Следующий пример импортирует
FormsModuleиз@angular/forms, потому что компонентContactComponentнаписан в стиле с управлением через шаблон. Модули с компонентами, написанными в стиле реактивного управления, импортируютReactiveFormsModule.
Селектор ContactComponent соответствует элементу с именем <app-contact>. Добавьте элемент с этим именем в шаблон AppComponent сразу под <app-title>:
src/app/app.component.ts (шаблон)
template: ` <app-title [subtitle]="subtitle"></app-title> <app-contact></app-contact> `
Компоненты форм часто бывают сложными. ContactComponent имеет свои ContactService и пользовательские трубы (называемые Awesome) и альтернативную версию HighlightDirective.
Для удобства разместите весь материал, связанный с контактами, в папке src/app/contact и разбейте компонент на три составляющих файла HTML, TypeScript и CSS:
<h2>Contact of {{userName}}</h2>
<div *ngIf="msg" class="msg">{{msg}}</div>
<form *ngIf="contacts" (ngSubmit)="onSubmit()" #contactForm="ngForm">
<h3 highlight>{{ contact.name | awesome }}</h3>
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" required
[(ngModel)]="contact.name"
name="name" #name="ngModel" >
<div [hidden]="name.valid" class="alert alert-danger">
Name is required
</div>
</div>
<br>
<button type="submit" class="btn btn-default" [disabled]="!contactForm.form.valid">Save</button>
<button type="button" class="btn" (click)="next()" [disabled]="!contactForm.form.valid">Next Contact</button>
<button type="button" class="btn" (click)="newContact()">New Contact</button>
</form>
import { Component, OnInit } from '@angular/core';
import { Contact, ContactService } from './contact.service';
import { UserService } from '../user.service';
@Component({
selector: 'app-contact',
templateUrl: './contact.component.html',
styleUrls: [ './contact.component.css' ]
})
export class ContactComponent implements OnInit {
contact: Contact;
contacts: Contact[];
msg = 'Loading contacts ...';
userName = '';
constructor(private contactService: ContactService, userService: UserService) {
this.userName = userService.userName;
}
ngOnInit() {
this.contactService.getContacts().then(contacts => {
this.msg = '';
this.contacts = contacts;
this.contact = contacts[0];
});
}
next() {
let ix = 1 + this.contacts.indexOf(this.contact);
if (ix >= this.contacts.length) { ix = 0; }
this.contact = this.contacts[ix];
}
onSubmit() {
// POST-DEMO TODO: do something like save it
this.displayMessage('Saved ' + this.contact.name);
}
newContact() {
this.displayMessage('New contact');
this.contact = {id: 42, name: ''};
this.contacts.push(this.contact);
}
/** Display a message briefly, then remove it. */
displayMessage(msg: string) {
this.msg = msg;
setTimeout(() => this.msg = '', 1500);
}
}
.ng-valid[required] {
border-left: 5px solid #42A948; /* green */
}
.ng-invalid {
border-left: 5px solid #a94442; /* red */
}
.alert {
padding: 15px;
margin: 8px 0;
border: 1px solid transparent;
border-radius: 4px;
}
.alert-danger {
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1;
}
.msg {
color: blue;
background-color: whitesmoke;
border: 1px solid transparent;
border-radius: 4px;
margin-bottom: 20px;
}
import { Injectable } from '@angular/core';
export class Contact {
constructor(public id: number, public name: string) { }
}
const CONTACTS: Contact[] = [
new Contact(21, 'Sam Spade'),
new Contact(22, 'Nick Danger'),
new Contact(23, 'Nancy Drew')
];
const FETCH_LATENCY = 500;
@Injectable()
export class ContactService {
getContacts() {
return new Promise<Contact[]>(resolve => {
setTimeout(() => { resolve(CONTACTS); }, FETCH_LATENCY);
});
}
getContact(id: number | string) {
return this.getContacts()
.then(heroes => heroes.find(hero => hero.id === +id));
}
}
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'awesome' })
/** Precede the input string with the word "Awesome " */
export class AwesomePipe implements PipeTransform {
transform(phrase: string) {
return phrase ? 'Awesome ' + phrase : '';
}
}
import { Directive, ElementRef } from '@angular/core';
@Directive({ selector: '[highlight], input' })
/** Highlight the attached element or an InputElement in blue */
export class HighlightDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = 'powderblue';
console.log(
`* Contact highlight called for ${el.nativeElement.tagName}`);
}
}
В середине шаблона компонента обратите внимание на двустороннюю привязку данных [(ngModel)]. ngModel — селектор для директивы NgModel.
Хотя NgModel является директивой Angular, компилятор Angular не распознает её по следующим причинам:
-
AppModuleне объявляетNgModel. -
NgModelне был импортирован черезBrowserModule.
Даже если Angular каким-то образом распознал ngModel, ContactComponent не будет вести себя как форма Angular, потому что такие функции форм, как валидация, ещё недоступны.
Импорт модуля FormsModule
Добавьте FormsModule в список imports метаданных AppModule.
imports: [ BrowserModule, FormsModule ],
Теперь привязка [(ngModel)] будет работать, и ввод пользователя будет валидироваться формами Angular, как только вы объявите новый компонент, трубу и директиву.
Не добавляйте
NgModel— илиFORMS_DIRECTIVES— в список объявлений метаданныхAppModule. Эти директивы относятся к модулюFormsModule.Компоненты, директивы и трубы принадлежат только одному модулю.
Никогда не переобъявляйте классы, которые принадлежат другому модулю.
Объявить компонент, директиву и трубу контактов
Приложение не будет компилироваться, пока вы не объявите компонент, директиву и трубу контактов. Обновите список declarations в AppModule:
src/app/app.module.ts (declarations)
declarations: [
AppComponent,
HighlightDirective,
TitleComponent,
AwesomePipe,
ContactComponent,
ContactHighlightDirective
],
Существует две директивы с одинаковым именем, обе называются
HighlightDirective.Чтобы обойти это, создайте псевдоним для версии контактов, используя ключевое слово импорта JavaScript
as.import { HighlightDirective as ContactHighlightDirective } from './contact/highlight.directive';Это решает непосредственную проблему ссылок на оба типа директив в одном файле, но оставляет нерешённую другую проблему. Вы узнаете об этой проблеме позже на этой странице в разделе Решение конфликтов директив.
Предоставить сервис ContactService
ContactComponent отображает контакты, полученные от сервиса ContactService, который Angular вводит в его конструктор.
Вам необходимо предоставить этот сервис где-то. Модуль ContactComponent мог бы предоставить его, но тогда сервис был бы ограничен только этим компонентом. Вы хотите поделиться этим сервисом с другими компонентами, связанными с контактами, которые вы, безусловно, добавите позже.
В этом приложении добавьте ContactService в список providers метаданных AppModule:
src/app/app.module.ts (providers)
providers: [ ContactService, UserService ],
Теперь вы можете ввести ContactService (например, UserService в любой компонент приложения.
Поставщики, охватывающие всё приложение
Поставщик
ContactServiceохватывает всё приложение, потому что Angular регистрирует поставщиков модуляprovidersв корневом инжекторе приложения.Архитектурно,
ContactServiceпринадлежит предметной области "Контакты". Классы в других областях не нуждаются вContactServiceи не должны его вводить.Вы могли бы ожидать, что Angular предложит механизм ограничения области действия модуля, чтобы обеспечить эту структуру. Этого нет. У экземпляров NgModule, в отличие от компонентов, нет собственных инжекторов, поэтому они не могут иметь собственных областей действия поставщиков.
Это упущение намеренное. NgModule предназначены в первую очередь для расширения приложения, для обогащения всего приложения возможностями модуля.
На практике ограничение области действия сервисов редко является проблемой. Компоненты, не относящиеся к контактам, не могут случайно ввести
ContactService. Чтобы ввестиContactService, необходимо сначала импортировать его тип. Только компоненты "Контакты" должны импортировать типContactService.Подробнее читайте в разделе Как ограничить область действия сервиса модулем? в разделе Вопросы и ответы по NgModule.
Запуск приложения
Всё готово для запуска приложения с редактором контактов.
Структура файлов приложения выглядит так:
app
app.component.ts
app.module.ts
highlight.directive.ts
title.component.(html|ts)
user.service.ts
contact
awesome.pipe.ts
contact.component.(css|html|ts)
contact.service.ts
highlight.directive.ts
Попробуйте пример:
Решение конфликтов директив
Проблема возникла ранее, когда вы объявили директиву контактов HighlightDirective, так как у вас уже был класс HighlightDirective на уровне приложения.
Селекторы обеих директив выделяют прикреплённый элемент разными цветами.
import { Directive, ElementRef } from '@angular/core';
@Directive({ selector: '[highlight]' })
/** Highlight the attached element in gold */
export class HighlightDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = 'gold';
console.log(
`* AppRoot highlight called for ${el.nativeElement.tagName}`);
}
}
import { Directive, ElementRef } from '@angular/core';
@Directive({ selector: '[highlight], input' })
/** Highlight the attached element or an InputElement in blue */
export class HighlightDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = 'powderblue';
console.log(
`* Contact highlight called for ${el.nativeElement.tagName}`);
}
}
Обе директивы объявлены в этом модуле, поэтому обе директивы активны.
Когда две директивы конкурируют за окраску одного и того же элемента, директива, объявленная позже, выигрывает, поскольку её изменения в DOM перезаписывают первые. В данном случае, директива контакта HighlightDirective делает заголовок приложения синим, когда он должен оставаться золотым.
Проблема в том, что две разные класса пытаются сделать одно и то же.
В порядке импортировать один и тот же класс директивы несколько раз. Angular удаляет дублирующие классы и регистрирует только один из них.
Но с точки зрения Angular, две разные класса, определённые в разных файлах, которые имеют одинаковое имя, не являются дубликатами. Angular сохраняет обе директивы, и они поочерёдно изменяют один и тот же HTML-элемент.
По крайней мере, приложение всё ещё компилируется. Если вы определите два разных класса компонента с тем же селектором, указывающим на тот же тег элемента, компилятор сообщит об ошибке. Он не может вставить два компонента в одно и то же место в DOM.
Чтобы устранить конфликты между компонентами и директивами, создавайте модули функций, которые изолируют объявления в одном модуле от объявлений в другом.
Модули функций
Это приложение ещё не большое, но оно уже сталкивается со структурными проблемами.
- Корневой
AppModuleрастёт с каждым новым классом приложения. - Существуют конфликтующие директивы. Директива
HighlightDirectiveв контакте перекрашивает работу, выполненную директивойHighlightDirective, объявленной вAppModule. Также она перекрашивает текст заголовка приложения, когда она должна окрашивать толькоContactComponent. - Приложение не имеет чётких границ между функциональностью контактов и другими функциями приложения. Это отсутствие ясности затрудняет распределение задач разработки между разными командами.
Вы можете решить эти проблемы с помощью модулей функций.
Модуль функции — это класс, украшенный декоратором @NgModule и его метаданными, точно так же, как корневой модуль. Метаданные модуля функций имеют те же свойства, что и метаданные корневого модуля.
Корневой модуль и модуль функции используют один и тот же контекст выполнения. Они используют один и тот же инжектор зависимостей, что означает, что сервисы в одном модуле доступны всем.
У модулей есть следующие существенные технические различия:
- Вы запускаете корневой модуль, чтобы запустить приложение; вы импортируете модуль функции, чтобы расширить приложение.
- Модуль функции может экспортировать или скрывать свою реализацию от других модулей.
В остальном модуль функции отличается в первую очередь своим назначением.
Модуль функции предоставляет связный набор функциональности, ориентированный на предметную область приложения, рабочий процесс пользователя, инструмент (формы, http, маршрутизация) или набор связанных утилит.
Хотя вы можете сделать всё в корневом модуле, модули функций помогают вам разбить приложение на области конкретного интереса и назначения.
Модуль функции взаимодействует с корневым модулем и другими модулями через предоставляемые им сервисы и компоненты, директивы и трубы, которые он разделяет.
В следующей секции вы вынесете функциональность контактов из корневого модуля в отдельный модуль функции.
Сделать Контакт модулем функции
Легко переработать материалы о контактах в модуль функции контактов.
- Создайте
ContactModuleв папкеsrc/app/contact. - Переместите материалы о контактах из
AppModuleвContactModule. - Замените импортированный
BrowserModuleнаCommonModule. - Импортируйте
ContactModuleвAppModule.
AppModule — единственный существующий класс, который меняется. Но вы добавляете один новый файл.
Добавить ContactModule
Вот новый ContactModule:
src/app/contact/contact.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { AwesomePipe } from './awesome.pipe';
import
{ ContactComponent } from './contact.component';
import { ContactService } from './contact.service';
import { HighlightDirective } from './highlight.directive';
@NgModule({
imports: [ CommonModule, FormsModule ],
declarations: [ ContactComponent, HighlightDirective, AwesomePipe ],
exports: [ ContactComponent ],
providers: [ ContactService ]
})
export class ContactModule { }
Вы копируете из AppModule операторы импорта, относящиеся к контактам, и свойства @NgModule, относящиеся к контактам, и вставляете их в ContactModule.
Вы импортируете FormsModule, потому что компоненту контакта это нужно.
Модули не наследуют доступ к компонентам, директивам или трубам, объявленным в других модулях. То, что
AppModuleимпортирует, не имеет отношения кContactModuleи наоборот. Прежде чемContactComponentсможет связаться с[(ngModel)], егоContactModuleдолжен импортироватьFormsModule.
Вы также заменили BrowserModule на CommonModule, по причинам, объяснённым в разделе Нужно ли импортировать BrowserModule или CommonModule? на странице Вопросы и ответы по NgModule.
Вы объявляете компонент, директиву и трубу контакта в модуле declarations.
Вы экспортируете ContactComponent, чтобы другие модули, которые импортируют ContactModule, могли включать его в шаблоны своих компонентов.
Все остальные объявленные классы контактов по умолчанию являются закрытыми. AwesomePipe и HighlightDirective скрыты от остальной части приложения. HighlightDirective больше не может окрасить AppComponent заголовок приложения.
Переработать AppModule
Вернитесь к AppModule и удалите всё, что специфично для функциональности контактов.
- Удалите операторы импорта контактов.
- Удалите объявления контактов и поставщиков контактов.
- Удалите
FormsModuleиз спискаimports(AppComponentэтого не нужно).
Оставьте только классы, необходимые на корневом уровне приложения.
Затем импортируйте ContactModule, чтобы приложение могло продолжать отображать экспортированный ContactComponent.
Вот переработанный вариант AppModule вместе с предыдущей версией.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
/* App Root */
import
{ AppComponent } from './app.component';
import { HighlightDirective } from './highlight.directive';
import { TitleComponent } from './title.component';
import { UserService } from './user.service';
/* Contact Imports */
import
{ ContactModule } from './contact/contact.module';
@NgModule({
imports: [ BrowserModule, ContactModule ],
declarations: [ AppComponent, HighlightDirective, TitleComponent ],
providers: [ UserService ],
bootstrap: [ AppComponent ],
})
export class AppModule { }
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
/* App Root */
import
{ AppComponent } from './app.component';
import { HighlightDirective } from './highlight.directive';
import { TitleComponent } from './title.component';
import { UserService } from './user.service';
/* Contact Imports */
import
{ ContactComponent } from './contact/contact.component';
import { ContactService } from './contact/contact.service';
import { AwesomePipe } from './contact/awesome.pipe';
import {
HighlightDirective as ContactHighlightDirective
} from './contact/highlight.directive';
import { FormsModule } from '@angular/forms';
@NgModule({
imports: [ BrowserModule, FormsModule ],
declarations: [
AppComponent, HighlightDirective, TitleComponent,
AwesomePipe, ContactComponent, ContactHighlightDirective
],
providers: [ ContactService, UserService ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
Улучшения
В переработанном AppModule много хорошего.
- Он не меняется по мере роста области Контакт.
- Он изменяется только при добавлении новых модулей.
- Он проще:
- Меньше операторов импорта.
- Отсутствует импорт
FormsModule. - Нет объявлений, специфичных для контактов.
- Нет поставщика
ContactService. - Нет конфликта
HighlightDirective.
Попробуйте эту ContactModule версию образца.
Попробуйте живой пример.
Ленивая загрузка модулей с помощью маршрутизатора
Пример приложения Heroic Staffing Agency развился. В нём есть ещё два модуля: один для управления героями в штате и другой для сопоставления кризисов с героями. Оба модуля находятся на ранних стадиях разработки. Их специфика не важна для истории, и на этой странице не обсуждаются все строки кода.
Просмотрите и загрузите полный исходный код этой версии с живого примера.
Некоторые аспекты текущего приложения заслуживают обсуждения:
- Приложение имеет три модуля функций: Контакт, Герой и Кризис.
- Маршрутизатор Angular помогает пользователям перемещаться между этими модулями.
ContactComponentявляется целевым по умолчанию при запуске приложения.ContactModuleпродолжает загружаться «жадно» при запуске приложения.-
HeroModuleиCrisisModuleзагружаются лениво.
Новый AppComponent шаблон имеет заголовок, три ссылки и <router-outlet>.
src/app/app.component.ts (v3 - Шаблон)
template: `
<app-title [subtitle]="subtitle"></app-title>
<nav>
<a routerLink="contact" routerLinkActive="active">Contact</a>
<a routerLink="crisis" routerLinkActive="active">Crisis Center</a>
<a routerLink="heroes" routerLinkActive="active">Heroes</a>
</nav>
<router-outlet></router-outlet>
`
Элемент <app-contact> исчез; теперь вы переходите на страницу Контакт.
AppModule незначительно изменился:
src/app/app.module.ts (v3)
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
/* App Root */
import { AppComponent } from './app.component.3';
import { HighlightDirective } from './highlight.directive';
import { TitleComponent } from './title.component';
import { UserService } from './user.service';
/* Feature Modules */
import { ContactModule } from './contact/contact.module.3';
/* Routing Module */
import { AppRoutingModule } from './app-routing.module.3';
@NgModule({
imports: [
BrowserModule,
ContactModule,
AppRoutingModule
],
providers: [ UserService ],
declarations: [ AppComponent, HighlightDirective, TitleComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
Некоторые имена файлов имеют расширение
.3, которое указывает на различие с предыдущими или будущими версиями. Существенные отличия будут объяснены в своё время.
Модуль по-прежнему импортирует ContactModule, чтобы его маршруты и компоненты загружались при запуске приложения.
Модуль не импортирует HeroModule или CrisisModule. Они будут загружаться и монтироваться асинхронно, когда пользователь перейдёт по одному из их маршрутов.
Существенное изменение с версии 2 — добавление AppRoutingModule в модуль imports. AppRoutingModule — это модуль маршрутизации, который обрабатывает маршрутные задачи приложения.
Маршрутизация приложения
src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
export const routes: Routes = [
{ path: '', redirectTo: 'contact', pathMatch: 'full'},
{ path: 'crisis', loadChildren: 'app/crisis/crisis.module#CrisisModule' },
{ path: 'heroes', loadChildren: 'app/hero/hero.module#HeroModule' }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
Маршрутизатор является предметом страницы Маршрутизация и навигация, поэтому этот раздел пропускает многие детали и сосредотачивается на пересечении NgModules и маршрутизации.
Файл app-routing.module.ts определяет три маршрута.
Первый маршрут перенаправляет пустой URL (например, http://host.com/) на другой маршрут с путём contact (например, http://host.com/contact).
Маршрут contact не определён здесь. Он определён в модуле маршрутизации функции Контакт, contact-routing.module.ts. Для модулей функций с компонентами маршрутизации принято определять собственные маршруты. Вы получите доступ к этому файлу через некоторое время.
Остальные два маршрута используют синтаксис ленивой загрузки, чтобы сообщить маршрутизатору, где найти модули:
{ path: 'crisis', loadChildren: 'app/crisis/crisis.module#CrisisModule' },
{ path: 'heroes', loadChildren: 'app/hero/hero.module#HeroModule' }
Расположение лениво загружаемого модуля — это строка, а не тип. В этом приложении строка идентифицирует как файл модуля, так и класс модуля, последний отделён от первого символом
#.
RouterModule.forRoot
Статический метод класса forRoot класса RouterModule с предоставленной конфигурацией и добавленный в массив imports обеспечивает маршрутные задачи для модуля.
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
Возвращаемый класс AppRoutingModule — это класс Routing Module, содержащий как директивы RouterModule , так и поставщики инъекции зависимостей, которые создают сконфигурированный Router.
Этот AppRoutingModule предназначен только для корневого модуля приложения.
Никогда не вызывайте
RouterModule.forRootв модуле маршрутизации функции.
Вернувшись в корневой AppModule, добавьте AppRoutingModule в его imports список, и приложение готово к навигации.
src/app/app.module.ts (imports)
imports: [ BrowserModule, ContactModule, AppRoutingModule ],
Переход к модулю функциональности
Папка src/app/contact содержит новый файл, contact-routing.module.ts. Он определяет маршрут contact, упомянутый ранее, и предоставляет ContactRoutingModule следующим образом:
src/app/contact/contact-routing.module.ts (routing)
@NgModule({
imports: [RouterModule.forChild([
{ path: 'contact', component: ContactComponent }
])],
exports: [RouterModule]
})
export class ContactRoutingModule {}
На этот раз вы передаете список маршрутов методу forChild модуля RouterModule. Список маршрутов отвечает только за предоставление дополнительных маршрутов и предназначен для модулей функциональности.
Всегда вызывайте
RouterModule.forChildв модуле маршрутизации функциональности.
forRoot и forChild — это стандартные имена методов, которые передают разные
importзначения корневому и функциональным модулям. Angular их не распознает, но разработчики Angular — да.Следуйте этой конвенции, если вы создаете аналогичный модуль, который содержит как общие декларативные элементы, так и сервисы.
ContactModule изменился в двух небольших, но важных аспектах.
@NgModule({
imports: [ CommonModule, FormsModule, ContactRoutingModule ],
declarations: [ ContactComponent, HighlightDirective, AwesomePipe ],
providers: [ ContactService ]
})
export class ContactModule { }
@NgModule({
imports: [ CommonModule, FormsModule ],
declarations: [ ContactComponent, HighlightDirective, AwesomePipe ],
exports: [ ContactComponent ],
providers: [ ContactService ]
})
export class ContactModule { }
- Он импортирует объект
ContactRoutingModuleизcontact-routing.module.ts. - Он больше не экспортирует
ContactComponent.
Теперь, когда вы переходите к ContactComponent с помощью маршрутизатора, нет причин делать его общедоступным. Кроме того, ContactComponent больше не нуждается в селекторе. Ни один шаблон никогда больше не будет ссылаться на этот ContactComponent. Он удален из шаблона AppComponent.
Загрузка по требованию маршрутизации к модулю
Загружаемые по требованию HeroModule и CrisisModule следуют тем же принципам, что и любой модуль функциональности. Они ничем не отличаются от загружаемых сразу ContactModule.
HeroModule немного сложнее, чем CrisisModule, что делает его более интересным и полезным примером. Его структура файлов следующая:
hero hero-detail.component.ts hero-list.component.ts hero.component.ts hero.module.ts hero-routing.module.ts hero.service.ts highlight.directive.ts
Это сценарий дочерней маршрутизации, знакомый читателям раздела Компонент дочерней маршрутизации страницы Маршрутизация и навигация. HeroComponent — это главный компонент функциональности и хост маршрутизации. Его шаблон содержит <router-outlet>, который отображает либо список героев (HeroList), либо редактор выбранного героя (HeroDetail). Оба компонента делегируют запросы и сохранение данных HeroService.
Еще один HighlightDirective окрашивает элементы в другой оттенок. В следующем разделе, Общие модули, вы устраните повторение и несоответствия.
HeroModule — это модуль функциональности, как и любой другой.
src/app/hero/hero.module.ts (класс)
@NgModule({
imports: [ CommonModule, FormsModule, HeroRoutingModule ],
declarations: [
HeroComponent, HeroDetailComponent, HeroListComponent,
HighlightDirective
]
})
export class HeroModule { }
Он импортирует FormsModule, потому что шаблон HeroDetailComponent связывается с [(ngModel)]. Он импортирует HeroRoutingModule из hero-routing.module.ts, как и ContactModule и CrisisModule.
CrisisModule практически не отличается.
Попробуйте пример вживую.
Общие модули
Приложение принимает форму. Но оно содержит три разные версии HighlightDirective. И множество файлов, засоряющих уровень папки app, можно было бы организовать лучше.
Добавьте SharedModule для хранения общих компонентов, директив и фильтров и поделитесь ими с модулями, которым они нужны.
- Создайте папку
src/app/shared. - Переместите
AwesomePipeиHighlightDirectiveизsrc/app/contactвsrc/app/shared. - Удалите классы
HighlightDirectiveизsrc/app/иsrc/app/hero. - Создайте класс
SharedModuleдля управления общими элементами. - Обновите другие модули функциональности для импорта
SharedModule.
Вот SharedModule:
src/app/src/app/shared/shared.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { AwesomePipe } from './awesome.pipe';
import { HighlightDirective } from './highlight.directive';
@NgModule({
imports: [ CommonModule ],
declarations: [ AwesomePipe, HighlightDirective ],
exports: [ AwesomePipe, HighlightDirective,
CommonModule, FormsModule ]
})
export class SharedModule { }
Обратите внимание на следующее:
- Он импортирует
CommonModule, потому что его компонент нуждается в общих директивах. - Он объявляет и экспортирует ожидаемые классы полезной функции, директивы и компонента.
- Он повторно экспортирует
CommonModuleиFormsModule
Повторный экспорт других модулей
Если вы просмотрите приложение, вы можете заметить, что многие компоненты, требующие директив SharedModule, также используют NgIf и NgFor из CommonModule и связываются со свойствами компонента с помощью [(ngModel)], директивы в FormsModule.
Вы можете уменьшить повторение, заставив SharedModule повторно экспортировать CommonModule и FormsModule, чтобы импортеры SharedModule получали CommonModule и FormsModule бесплатно.
Как это ни странно, компоненты, объявленные самим SharedModule, не связываются с [(ngModel)]. Технически SharedModule не нужно импортировать FormsModule.
SharedModule по-прежнему может экспортировать FormsModule без его включения в imports.
Почему TitleComponent не является общим
SharedModule предназначен для обеспечения доступа к часто используемым компонентам, директивам и фильтрам в шаблонах компонентов во многих других модулях.
TitleComponent используется только один раз в AppComponent. Нет смысла его разделять.
Почему UserService не является общим
Хотя многие компоненты используют одни и те же экземпляры сервисов, они полагаются на внедрение зависимостей Angular для этого вида совместного использования, а не на систему модулей.
Несколько компонентов в образце используют UserService. Должен быть только один экземпляр UserService во всем приложении и только один поставщик.
UserService — это одноэлементный объект во всём приложении. Вам не нужно, чтобы каждый модуль имел свой собственный экземпляр. Тем не менее, есть опасность этого, если SharedModule предоставляет UserService.
Не указывайте одноэлементный объект
providersво всём приложении в общем модуле. Модуль, загруженный по требованию, который импортирует этот общий модуль, создаёт собственную копию сервиса.
Модуль ядра
В данный момент корневая папка заполнена UserService и TitleComponent, которые появляются только в корневом AppComponent. Вы не включили их в SharedModule по причинам, только что объяснённым.
Вместо этого, объедините их в один CoreModule, который импортируется один раз при запуске приложения и больше нигде.
Выполните следующие шаги:
- Создайте папку
src/app/core. - Переместите
UserServiceиTitleComponentизsrc/app/вsrc/app/core. - Создайте класс
CoreModuleдля управления ядром. - Обновите модуль
AppRootдля импортаCoreModule.
Большая часть этой работы знакома. Интересной частью является CoreModule.
src/app/src/app/core/core.module.ts
import {
ModuleWithProviders, NgModule,
Optional, SkipSelf } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TitleComponent } from './title.component';
import { UserService } from './user.service';
@NgModule({
imports: [ CommonModule ],
declarations: [ TitleComponent ],
exports: [ TitleComponent ],
providers: [ UserService ]
})
export class CoreModule {
}
Вы импортируете несколько дополнительных символов из библиотеки ядра Angular, которые пока не используете. Они станут актуальными позже на этой странице.
Метаданные @NgModule должны быть вам знакомы. Вы объявляете TitleComponent, потому что этот модуль его владеет, и экспортируете его, потому что AppComponent (который находится в AppModule) отображает заголовок в своём шаблоне. TitleComponent нуждается в директиве Angular NgIf, которую вы импортируете из CommonModule.
CoreModule предоставляет UserService. Angular регистрирует этого поставщика с инжектором корня приложения, делая доступным одноэлементный экземпляр UserService для любого компонента, которому он нужен, независимо от того, загружается ли этот компонент сразу или по требованию.
Зачем это нужно?
Этот сценарий явно искусственный. Приложение слишком мало, чтобы беспокоиться об одном файле сервиса и крошечном, одноразовом компоненте.
Файл
TitleComponentв корневой папке никому не мешает. КорневойAppModuleможет зарегистрироватьUserServiceсам, как это происходит сейчас, даже если вы решите перенести файлUserServiceв папкуsrc/app/core.Приложения в реальном мире имеют больше причин для беспокойства. В них может быть несколько одноразовых компонентов (например, индикаторы загрузки, всплывающие сообщения и модальные диалоги), которые появляются только в шаблоне
AppComponent.Приложения часто содержат много одноэлементных сервисов, например,
UserServiceв этом примере. Каждый из них должен быть зарегистрирован ровно один раз в инжекторе корня приложения при запуске приложения.Хотя многие компоненты используют такие сервисы в своих конструкторах и поэтому требуют, чтобы JavaScript
importимпортировал их символы, ни один другой компонент или модуль не должен определять или повторно создавать эти сервисы сами. Их поставщики не общие.Рекомендуется собирать такие одноразовые классы и скрывать их детали внутри
CoreModule. Упрощённое корневоеAppModuleимпортируетCoreModuleв своей роли координатора приложения в целом.
Очистка
После рефакторинга модулей ядра и функциональности пришло время очистить другие модули.
Оптимизация AppModule
Вот обновлённый AppModule вместе с версией 3 для сравнения:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
/* App Root */
import { AppComponent } from './app.component';
/* Feature Modules */
import { ContactModule } from './contact/contact.module';
import { CoreModule } from './core/core.module';
/* Routing Module */
import { AppRoutingModule } from './app-routing.module';
@NgModule({
imports: [
BrowserModule,
ContactModule,
CoreModule,
AppRoutingModule
],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
/* App Root */
import { AppComponent } from './app.component.3';
import { HighlightDirective } from './highlight.directive';
import { TitleComponent } from './title.component';
import { UserService } from './user.service';
/* Feature Modules */
import { ContactModule } from './contact/contact.module.3';
/* Routing Module */
import { AppRoutingModule } from './app-routing.module.3';
@NgModule({
imports: [
BrowserModule,
ContactModule,
AppRoutingModule
],
providers: [ UserService ],
declarations: [ AppComponent, HighlightDirective, TitleComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
AppModule теперь обладает следующими качествами:
- Немного меньше, так как многие
src/app/rootклассы были перемещены в другие модули. - Стабильнее, так как вы будете добавлять будущие компоненты и провайдеры в другие модули, а не в этот.
- Делегирует работу импортированным модулям, вместо того чтобы выполнять её самостоятельно.
- Фокусируется на своей основной задаче — координации приложения в целом.
Оптимизированный модуль ContactModule
Вот новый ContactModule в паре со старой версией:
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { ContactComponent } from './contact.component';
import { ContactService } from './contact.service';
import { ContactRoutingModule } from './contact-routing.module';
@NgModule({
imports: [ SharedModule, ContactRoutingModule ],
declarations: [ ContactComponent ],
providers: [ ContactService ]
})
export class ContactModule { }
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { AwesomePipe } from './awesome.pipe';
import { ContactComponent } from './contact.component.3';
import { ContactService } from './contact.service';
import { HighlightDirective } from './highlight.directive';
import { ContactRoutingModule } from './contact-routing.module.3';
@NgModule({
imports: [ CommonModule, FormsModule, ContactRoutingModule ],
declarations: [ ContactComponent, HighlightDirective, AwesomePipe ],
providers: [ ContactService ]
})
export class ContactModule { }
Обратите внимание на следующее:
AwesomePipeиHighlightDirectiveотсутствуют.- Импорты включают
SharedModuleвместоCommonModuleиFormsModule. - Новая версия более лаконичная и чистая.
Настройка основных сервисов с помощью CoreModule.forRoot
Модуль, добавляющий провайдеры в приложение, может также предоставлять средства для их настройки.
По соглашению, статический метод forRoot одновременно предоставляет и настраивает сервисы. Он принимает объект конфигурации сервиса и возвращает ModuleWithProviders, который представляет собой простой объект со следующими свойствами:
-
ngModule: классCoreModule -
providers: настроенные провайдеры
Основной AppModule импортирует CoreModule и добавляет providers к провайдерам AppModule
Точнее, Angular накапливает все импортированные провайдеры перед добавлением элементов, перечисленных в
@NgModule.providers. Эта последовательность гарантирует, что всё, что вы явно добавляете в провайдерыAppModule, будет иметь приоритет перед провайдерами импортированных модулей.
Добавьте метод CoreModule.forRoot, который настраивает основной UserService.
Вы расширили основной UserService с необязательным, инжектируемым UserServiceConfig. Если UserServiceConfig существует, UserService устанавливает имя пользователя из этой конфигурации.
src/app/core/user.service.ts (конструктор)
constructor(@Optional() config: UserServiceConfig) {
if (config) { this._userName = config.userName; }
}
Вот CoreModule.forRoot, который принимает объект UserServiceConfig:
src/app/core/core.module.ts (forRoot)
static forRoot(config: UserServiceConfig): ModuleWithProviders {
return {
ngModule: CoreModule,
providers: [
{provide: UserServiceConfig, useValue: config }
]
};
}
Наконец, вызовите его в списке imports модуля AppModule.
src/app//app.module.ts (импорты)
imports: [
BrowserModule,
ContactModule,
CoreModule.forRoot({userName: 'Miss Marple'}),
AppRoutingModule
],
Приложение отображает "Мисс Марпл" в качестве пользователя вместо стандартного "Шерлок Холмс".
Вызов
forRootдолжен выполняться только в корневом модуле приложения,AppModule. Вызов в любом другом модуле, особенно в модуле, загружаемом по требованию, противоречит задумке и может привести к ошибке во время выполнения.Не забудьте импортировать результат; не добавляйте его в другой список
@NgModule.
Предотвращение повторного импорта CoreModule
Только корневой AppModule должен импортировать CoreModule. Проблемы могут возникнуть, если модуль, загружаемый по требованию, импортирует его.
Можно надеяться, что никто не допустит этой ошибки. Или же можно защититься от неё, добавив следующий CoreModule конструктор.
constructor (@Optional() @SkipSelf() parentModule: CoreModule) {
if (parentModule) {
throw new Error(
'CoreModule is already loaded. Import it in the AppModule only');
}
}
Конструктор сообщает Angular, что нужно ввести CoreModule в него. Это выглядит опасно циклически.
Инъекция была бы циклической, если бы Angular искал CoreModule в текущем инжекторе. Декоратор @SkipSelf означает "искать CoreModule в родительском инжекторе, выше меня в иерархии инжектора."
Если конструктор выполняется так, как предполагается, в AppModule, не существует родительского инжектора, который мог бы предоставить экземпляр CoreModule. Инжектор должен отказаться.
По умолчанию инжектор выдает ошибку, когда не может найти запрашиваемого провайдера. Декоратор @Optional означает, что отсутствие сервиса приемлемо. Инжектор возвращает null, параметр parentModule имеет значение null, и конструктор завершается без происшествий.
Это другое дело, если вы неправильно импортируете CoreModule в модуль, загружаемый по требованию, например, HeroModule (попробуйте).
Angular создаёт модуль, загружаемый по требованию, со своим инжектором, являющимся дочерним элементом корневого инжектора. @SkipSelf заставляет Angular искать CoreModule в родительском инжекторе, на этот раз в корневом инжекторе. Конечно, он находит экземпляр, импортированный корневым AppModule. Теперь parentModule существует, и конструктор генерирует ошибку.
Заключение
Вы это сделали! Вы можете изучить и загрузить весь исходный код этой окончательной версии из демонстрационного примера.
Часто задаваемые вопросы
Теперь, когда вы понимаете NgModules, вас может заинтересовать связанная страница Вопросы и ответы по NgModule с готовыми ответами на конкретные вопросы по проектированию и реализации.
© 2010–2017 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://v2.angular.io/docs/ts/latest/guide/ngmodule.html