Вопросы и ответы по NgModule
Ответы на часто задаваемые вопросы о @NgModule.
NgModules помогают организовать приложение в связные блоки функциональности.
Страница NgModules проведет вас от самых элементарных @NgModule до многогранного примера с модулями, загружаемыми по требованию.
Эта страница отвечает на вопросы многих разработчиков об архитектуре и реализации NgModule.
В этих ответах предполагается, что вы прочитали страницу NgModules.
Объявления
- Какие классы следует добавлять в раздел объявлений?
- Что такое объявляемый класс?
- Какие классы не следует добавлять в раздел объявлений?
- Почему один и тот же компонент указывается в нескольких свойствах NgModule?
- Что означает сообщение об ошибке "Can't bind to 'x' since it isn't a known property of 'y'"?
Импорты
- Что следует импортировать?
- Следует ли импортировать BrowserModule или CommonModule?
- Что делать, если один и тот же модуль импортируется дважды?
Экспорты
- Что следует экспортировать?
- Что не следует экспортировать?
- Можно ли повторно экспортировать импортированные классы и модули?
- Что делает метод forRoot?
Услуги
- Почему сервис, предоставленный в модуле функциональности, виден везде?
- Почему сервис, предоставленный в модуле, загружаемом по требованию, виден только в этом модуле?
- Что произойдёт, если два модуля предоставляют один и тот же сервис?
- Как ограничить область действия сервиса модулем?
- Следует ли добавлять сервисы, доступные во всём приложении, в основной модуль AppModule или основной компонент AppComponent?
- Следует ли добавлять другие сервисы в модуль или в компонент?
- Почему плохо, если SharedModule предоставляет сервис модулю, загружаемому по требованию?
- Почему при загрузке по требованию создается дочерний инжектор?
- Как узнать, был ли ранее загружен модуль или сервис?
Компоненты входа
- Что такое компонент входа?
- В чём разница между компонентом запуска и компонентом входа?
- Когда нужно добавлять компоненты в entryComponents?
- Зачем Angular нужны entryComponents?
Общие вопросы
- Какие модули следует использовать и как?
- В чём разница между Angular и JavaScript модулями?
- Как Angular находит компоненты, директивы и трубки в шаблоне?
- Что такое «ссылка на шаблон»?
- Что такое компилятор Angular?
- Можно ли обобщить API NgModule?
Какие классы следует добавлять в раздел объявлений?
Добавляйте объявляемые классы — компоненты, директивы и трубки — в список declarations.
Объявляйте эти классы ровно в одном модуле приложения. Объявляйте их в этом модуле, если они относятся к этому модулю.
Что такое объявляемый класс?
Объявляемые классы — это типы классов — компоненты, директивы и трубки — которые можно добавить в список declarations модуля. Они являются единственными классами, которые можно добавить в declarations.
Какие классы не следует добавлять в раздел объявлений?
В список declarations модуля добавляйте только объявляемые классы.
Не объявляйте:
- Класс, уже объявленный в другом модуле, будь то модуль приложения, @NgModule или модуль сторонних разработчиков.
- Массив директив, импортированных из другого модуля. Например, не объявляйте FORMS_DIRECTIVES из
@angular/forms. - Классы модулей.
- Классы сервисов.
- Классы и объекты, не являющиеся частью Angular (например, строки, числа, функции, модели сущностей, конфигурации, бизнес-логика и вспомогательные классы).
Почему один и тот же компонент указывается в нескольких свойствах NgModule?
AppComponent часто указывается как в declarations, так и в bootstrap. Вы можете увидеть HeroComponent в declarations, exports, и entryComponents.
Хотя это может показаться избыточным, эти свойства выполняют разные функции. Принадлежность к одному списку не подразумевает принадлежности к другому.
-
AppComponentможет быть объявлен в этом модуле, но не запущен. -
AppComponentможет быть запущен в этом модуле, но объявлен в другом модуле функциональности. -
HeroComponentможет быть импортирован из другого модуля приложения (поэтому вы не можете его объявить) и повторно экспортирован этим модулем. -
HeroComponentможет быть экспортирован для включения в шаблон внешнего компонента, а также динамически загружен в диалоговое окно всплывающего типа.
Что означает ошибка «Невозможно привязаться к 'x', так как это не известное свойство 'y'»?
Эта ошибка, как правило, означает, что вы не объявили директиву «x» или не импортировали модуль, к которому принадлежит «x».
Например, если «x» — это ngModel, вы, вероятно, не импортировали FormsModule из @angular/forms.
Возможно, вы объявили «x» в подмодуле приложения, но забыли его экспортировать? Класс «x» не виден другим модулям, пока вы не добавите его в список exports.
Какие модули следует импортировать?
Импортируйте модули, чьи публичные (экспортированные) декларируемые классы вам нужно использовать в шаблонах компонентов этого модуля.
Это всегда означает импорт CommonModule из @angular/common для доступа к угловым директивам, таким как NgIf и NgFor. Вы можете импортировать его напрямую или из другого модуля, который переэкспортирует его.
Импортируйте FormsModule из @angular/forms, если ваши компоненты содержат [(ngModel)] двунаправленные выражения привязки.
Импортируйте общие и функциональные модули, когда компоненты этого модуля используют их компоненты, директивы и трубы.
Импортируйте только BrowserModule в корневой AppModule.
Должен ли я импортировать BrowserModule или CommonModule?
Корневой модуль приложения (AppModule) почти каждого приложения для браузера должен импортировать BrowserModule из @angular/platform-browser.
BrowserModule предоставляет сервисы, которые необходимы для запуска и работы приложения браузера.
BrowserModule также повторно экспортирует CommonModule из @angular/common, что означает, что компоненты в модуле AppModule также имеют доступ к угловым директивам, необходимым для любого приложения, таким как NgIf и NgFor.
Не импортируйте BrowserModule ни в один другой модуль. Функциональные модули и модули с отложенной загрузкой должны импортировать CommonModule вместо этого. Им нужны общие директивы. Им не нужно повторно устанавливать поставщики для всего приложения.
BrowserModuleвыдает ошибку, если вы пытаетесь выполнить отложенную загрузку модуля, который его импортирует.
Импорт CommonModule также освобождает функциональные модули для использования на любой целевой платформе, а не только на браузерах.
Что делать, если я импортирую один и тот же модуль дважды?
Это не проблема. Когда три модуля импортируют модуль 'A', Angular оценивает модуль 'A' один раз, когда встречает его впервые, и не делает этого снова.
Это верно на любом уровне, на котором A появляется в иерархии импортированных модулей. Когда модуль 'B' импортирует модуль 'A', модуль 'C' импортирует 'B', а модуль 'D' импортирует [C, B, A], тогда 'D' вызывает оценку 'C', который вызывает оценку 'B', который оценивает 'A'. Когда Angular доходит до 'B' и 'A' в 'D', они уже кэшированы и готовы к использованию.
Angular не любит модули с циклическими ссылками, поэтому не позволяйте модулю 'A' импортировать модуль 'B', который импортирует модуль 'A'.
Что нужно экспортировать?
Экспортируйте декларируемые классы, к которым компоненты в других модулях могут обращаться в своих шаблонах. Это ваши публичные классы. Если вы не экспортируете класс, он остается приватным, видимым только для других компонентов, объявленных в этом модуле.
Вы можете экспортировать любой декларируемый класс — компоненты, директивы и трубы — независимо от того, объявлен ли он в этом модуле или в импортированном модуле.
Вы можете переэкспортировать целые импортированные модули, что эффективно переэкспортирует все их экспортированные классы. Модуль даже может экспортировать модуль, который он не импортирует.
Что не следует экспортировать?
Не экспортируйте следующее:
- Приватные компоненты, директивы и трубы, которые вам нужны только внутри компонентов, объявленных в этом модуле. Если вы не хотите, чтобы другой модуль их видел, не экспортируйте их.
- Недекларируемые объекты, такие как сервисы, функции, конфигурации и модели сущностей.
- Компоненты, которые загружаются только динамически маршрутизатором или при запуске. Такие компоненты входа никогда не могут быть выбраны в шаблоне другого компонента. Хотя нет вреда в их экспорте, нет и пользы.
- Чистые модули сервисов, которые не имеют публичных (экспортированных) объявлений. Например, нет смысла повторно экспортировать
HttpModule, потому что он ничего не экспортирует. Его единственная цель — добавить поставщиков http-сервиса ко всему приложению.
Можно ли переэкспортировать классы и модули?
Конечно.
Модули — отличный способ выборочно объединять классы из других модулей и переэкспортировать их в объединенный, удобный модуль.
Модуль может переэкспортировать целые модули, что эффективно переэкспортирует все их экспортированные классы. Собственный BrowserModule Angular экспортирует несколько модулей таким образом:
exports: [CommonModule, ApplicationModule]
Модуль может экспортировать комбинацию собственных объявлений, выбранных импортированных классов и импортированных модулей.
Не нужно повторно экспортировать чисто сервисные модули. Чисто сервисные модули не экспортируют декларируемые классы, которые другой модуль мог бы использовать. Например, нет смысла повторно экспортировать
HttpModule, так как он ничего не экспортирует. Его единственная цель — добавление поставщиков http-сервисов в приложение в целом.
Что такое метод forRoot?
Статический метод forRoot — это соглашение, которое упрощает разработчикам конфигурирование поставщиков модуля.
Метод RouterModule.forRoot — хороший пример. Приложения передают объект Routes в RouterModule.forRoot для конфигурирования сервиса Router для всего приложения с маршрутами. RouterModule.forRoot возвращает ModuleWithProviders. Вы добавляете этот результат в список imports корневого модуля AppModule.
Вызывайте и импортируйте результат
.forRootтолько в корневом модуле приложения,AppModule. Импорт в любой другой модуль, особенно в отложенно загружаемом модуле, противоречит задумке и, скорее всего, приведёт к ошибке во время выполнения.
RouterModule также предлагает статический метод forChild для конфигурации маршрутов отложенно загружаемых модулей.
forRoot и forChild — это условные названия методов, которые конфигурируют сервисы в корневом и функциональном модулях соответственно.
Angular не распознаёт эти названия, но разработчики Angular понимают их. Следуйте этой конвенции при написании подобных модулей с настраиваемыми поставщиками сервисов.
Почему сервис, предоставленный в функциональном модуле, виден везде?
Поставщики, указанные в @NgModule.providers запущенного модуля, имеют глобальный охват. Добавление поставщика сервиса в @NgModule.providers эффективно публикует сервис для всего приложения.
При импорте модуля Angular добавляет поставщиков сервисов модуля (содержание его списка providers) в корневой инжектор приложения.
Это делает поставщика видимым для любого класса в приложении, который знает маркер поиска поставщика.
Это сделано по дизайну. Расширяемость через импорт модулей является основной целью системы NgModule. Объединение поставщиков модулей в инжектор приложения облегчает библиотекам модулей обогащение всего приложения новыми сервисами. Добавив HttpModule один раз, любой компонент приложения может выполнить http-запросы.
Однако это может показаться неожиданным, если вы ожидаете, что сервисы модуля будут видны только компонентам, объявленным этим функциональным модулем. Если HeroModule предоставляет HeroService, а корневой AppModule импортирует HeroModule, любой класс, знающий тип HeroService, может ввести этот сервис, а не только классы, объявленные в HeroModule.
Почему сервис, предоставляемый в отложенно загружаемом модуле, виден только этому модулю?
В отличие от поставщиков модулей, загружаемых при запуске, поставщики отложенно загружаемых модулей имеют объём модуля.
Когда маршрутизатор Angular откладывает загрузку модуля, он создаёт новый контекст выполнения. Этот контекст имеет свой инжектор, являющийся прямым потомком инжектора приложения.
Маршрутизатор добавляет поставщиков отложенно загружаемого модуля и поставщиков импортированных модулей в этот дочерний инжектор.
Эти поставщики изолированы от изменений в поставщиках приложения с тем же маркером поиска. Когда маршрутизатор создаёт компонент в контексте отложенно загружаемого модуля, Angular отдаёт предпочтение экземплярам сервиса, созданным из этих поставщиков, экземплярам сервиса корневого инжектора приложения.
Что произойдёт, если два модуля предоставят один и тот же сервис?
Когда два импортированных модуля, загруженные одновременно, перечисляют поставщика с тем же маркером, поставщик второго модуля "выигрывает". Это связано с тем, что оба поставщика добавлены в один и тот же инжектор.
Когда Angular ищет инъекцию сервиса для этого маркера, он создаёт и предоставляет экземпляр, созданный вторым поставщиком.
Каждый класс, который вводит этот сервис, получает экземпляр, созданный вторым поставщиком. Даже классы, объявленные в первом модуле, получают экземпляр, созданный вторым поставщиком.
Если модуль A предоставляет сервис для маркера 'X' и импортирует модуль B, который также предоставляет сервис для маркера 'X', то определение сервиса модуля A "выигрывает".
Сервис, предоставляемый корневым AppModule, имеет приоритет над сервисами, предоставляемыми импортированными модулями. AppModule всегда выигрывает.
Как ограничить область сервиса модулем?
Когда модуль загружается при запуске приложения, его @NgModule.providers имеют глобальный охват; то есть они доступны для инъекции во всём приложении.
Импортированные поставщики легко заменяются поставщиками из другого импортированного модуля. Такая замена может быть предусмотрена дизайном. Она может быть непреднамеренной и иметь нежелательные последствия.
В качестве общего правила, импортируйте модули с поставщиками ровно один раз, предпочтительно в корневом модуле приложения. Это также, как правило, лучшее место для их конфигурации, обертывания и переопределения.
Предположим, что модулю требуется настраиваемый HttpBackend, который добавляет специальный заголовок для всех запросов Http. Если другой модуль в другом месте приложения также настраивает HttpBackend или просто импортирует HttpModule, он может переопределить поставщика HttpBackend этого модуля, потеряв специальный заголовок. Сервер отклонит запросы http от этого модуля.
Чтобы избежать этой проблемы, импортируйте HttpModule только в AppModule, корневой модуль приложения.
Если вам необходимо защититься от такого рода «порчи поставщика», не полагайтесь на providers модуля, загружаемого во время запуска.
Если можно, загружайте модуль лениво. Angular предоставляет лениво загружаемому модулю собственный дочерний инжектор. Поставщики модуля видны только в дереве компонентов, созданном с помощью этого инжектора.
Если вы должны загрузить модуль принудительно, когда приложение запускается, укажите сервис в компоненте вместо этого.
Продолжая тот же пример, предположим, что компоненты модуля действительно требуют частного, настраиваемого HttpBackend.
Создайте «главный компонент», который действует как корень для всех компонентов модуля. Добавьте поставщика настраиваемого HttpBackend в список providers главного компонента, а не в providers модуля. Помните, что Angular создаёт дочерний инжектор для каждого экземпляра компонента и заполняет инжектор собственными поставщиками компонента.
Когда дочерний компонент запрашивает сервис HttpBackend, Angular предоставляет локальный сервис HttpBackend, а не версию, предоставленную в корневом инжекторе приложения. Дочерние компоненты выполняют правильные запросы http независимо от того, что другие модули делают с HttpBackend.
Убедитесь, что компоненты модуля созданы как дочерние компоненты главного компонента этого модуля.
Вы можете встроить дочерние компоненты в шаблон главного компонента. Кроме того, сделайте главный компонент хостом маршрутизации, предоставив ему <router-outlet>. Определите дочерние маршруты и позвольте маршрутизатору загружать компоненты модуля в этот разъем.
Следует ли добавлять поставщиков, действующих по всему приложению, в корневой модуль AppModule или корневой компонент AppComponent?
Регистрируйте поставщиков, действующих по всему приложению, в корневом AppModule, а не в AppComponent.
Лениво загружаемые модули и их компоненты могут вводить сервисы AppModule; они не могут вводить сервисы AppComponent.
Регистрируйте сервис в поставщиках AppComponent только если сервис должен быть скрыт от компонентов за пределами дерева AppComponent. Это редкий случай использования.
В более общем плане, предпочтительнее регистрировать поставщиков в модулях, чем регистрировать их в компонентах.
Обсуждение
Angular регистрирует всех поставщиков стартового модуля с корневым инжектором приложения. Сервисы, созданные из поставщиков корневого инжектора, доступны всему приложению. Они обладают глобальным объёмом.
Некоторые сервисы (например, Router) работают только при регистрации в корневом инжекторе приложения.
Напротив, Angular регистрирует поставщиков AppComponent с собственным инжектором AppComponent . Сервисы AppComponent доступны только этому компоненту и его дереву компонентов. Они обладают локальным объёмом.
Инжектор AppComponent является дочерним по отношению к корневому инжектору, на один уровень ниже в иерархии инжектора. Для приложений, которые не используют маршрутизатор, это почти всё приложение. Но для маршрутизируемых приложений «почти» недостаточно.
Сервисы AppComponent не существуют на корневом уровне, где работает маршрутизация. Лениво загруженные модули не могут к ним обратиться. В примерах приложений в разделе NgModule, если бы вы зарегистрировали UserService в AppComponent, HeroComponent не смог бы его ввести. Приложение потерпит неудачу в тот момент, когда пользователь перейдёт к «Героям».
Следует ли добавлять другие поставщики в модуль или компонент?
В общем случае предпочтительнее регистрировать поставщиков, специфичных для функции, в модулях (@NgModule.providers) вместо регистрации в компонентах (@Component.providers).
Зарегистрируйте поставщика в компоненте, когда вы обязаны ограничить область экземпляра сервиса этим компонентом и его деревом компонентов. Примените тот же принцип к регистрации поставщика с директивой.
Например, компонент редактирования героя, которому нужна частная копия кэширующего сервиса героя, должен зарегистрировать HeroService с HeroEditorComponent . Затем каждый новый экземпляр HeroEditorComponent получает свой собственный экземпляр кэшированного сервиса. Изменения, которые редактор вносит в героев в своём сервисе, не затрагивают экземпляры героев в других частях приложения.
Всегда регистрируйте глобальные сервисы в корневом AppModule, а не в корневом AppComponent.
Почему плохо, если SharedModule предоставляет сервис лениво загружаемому модулю?
Этот вопрос рассматривается в разделе Почему UserService не общий на странице NgModules, в котором обсуждается важность исключения поставщиков из SharedModule.
Предположим, UserService был указан в providers модуля (что не так). Предположим, каждый модуль импортирует этот SharedModule (что они все делают).
При запуске приложения Angular жадно загружает AppModule и ContactModule.
Оба экземпляра импортированного SharedModule предоставят UserService. Angular регистрирует один из них в корневом инжекторе приложения (см. Что делать, если я импортирую один и тот же модуль дважды?). Затем какой-то компонент вводит UserService, Angular находит его в корневом инжекторе приложения и предоставляет приложение-широкий синглтон UserService. Без проблем.
Теперь рассмотрим HeroModule (который загружается лениво).
Когда маршрутизатор лениво загружает HeroModule, он создаёт дочерний инжектор и регистрирует поставщика UserService в этом дочернем инжекторе. Дочерний инжектор не является корневым инжектором.
Когда Angular создаёт лениво загружаемый HeroComponent, он должен ввести UserService. На этот раз он находит поставщика UserService в дочернем инжекторе ленивого модуля и создаёт новый экземпляр UserService. Это совершенно другой экземпляр UserService по сравнению с приложением-широким синглтоном, который Angular ввёл в одном из загруженных неотложенно компонентов.
Это почти наверняка ошибка.
Для демонстрации запустите живой пример. Измените
SharedModuleтаким образом, чтобы он предоставлялUserServiceвместоCoreModule. Затем несколько раз переключайтесь между ссылками "Контакты" и "Герои". Имя пользователя будет меняться, так как Angular каждый раз создаёт новый экземплярUserService.
Почему ленивая загрузка создаёт дочерний инжектор?
Angular добавляет @NgModule.providers в корневой инжектор приложения, если модуль не загружается лениво. Для лениво загружаемого модуля Angular создаёт дочерний инжектор и добавляет поставщиков модуля в дочерний инжектор.
Это означает, что модуль ведёт себя по-разному в зависимости от того, загружается ли он при запуске приложения или лениво позже. Игнорирование этого различия может привести к негативным последствиям.
Почему Angular не добавляет лениво загружаемых поставщиков в корневой инжектор приложения, как это делает для неотложенно загружаемых модулей?
Ответ основан на фундаментальной характеристике системы инъекции зависимостей Angular. Инжектор может добавлять поставщиков до первого использования. Как только инжектор начинает создавать и предоставлять сервисы, его список поставщиков замораживается; новые поставщики не разрешаются.
Когда приложение запускается, Angular сначала настраивает корневой инжектор с поставщиками всех неотложенно загружаемых модулей перед созданием первого компонента и введением любых предоставляемых сервисов. После запуска приложения корневой инжектор закрыт для новых поставщиков.
Проходит время, и логика приложения запускает ленивую загрузку модуля. Angular должен добавить поставщиков лениво загружаемого модуля в какой-то инжектор. Он не может добавить их в корневой инжектор приложения, потому что этот инжектор закрыт для новых поставщиков. Поэтому Angular создаёт новый дочерний инжектор для лениво загружаемого модуля.
Как узнать, был ли модуль или служба загружены ранее?
Некоторые модули и их службы должны загружаться корневым AppModule только один раз. Импорт модуля второй раз, лениво загрузив модуль, может привести к ошибкам, которые могут быть трудно обнаружить и диагностировать.
Чтобы предотвратить эту проблему, напишите конструктор, который пытается ввести модуль или службу из корневого инжектора приложения. Если введение удачно, класс был загружен второй раз. Вы можете выбросить ошибку или предпринять другие меры.
Некоторые NgModules (такие как BrowserModule) реализуют такую защиту, например, этот конструктор CoreModule из страницы NgModules.
src/app/core/core.module.ts (Конструктор)
constructor (@Optional() @SkipSelf() parentModule: CoreModule) {
if (parentModule) {
throw new Error(
'CoreModule is already loaded. Import it in the AppModule only');
}
}
Что такое компонент входа?
Компонент входа — это любой компонент, который Angular загружает в принудительном порядке по типу.
Компонент, загруженный декларативно через свой селектор, не является компонентом входа.
Большинство компонентов приложения загружаются декларативно. Angular использует селектор компонента для поиска элемента в шаблоне. Затем он создаёт HTML-представление компонента и вставляет его в DOM в выбранный элемент. Это не компоненты входа.
Несколько компонентов загружаются только динамически и никогда не ссылаются в шаблоне компонента.
Загруженный корневой AppComponent — это компонент входа. Да, его селектор соответствует тегу элемента в index.html. Но index.html — это не шаблон компонента, и селектор AppComponent не соответствует элементу в любом шаблоне компонента.
Angular загружает AppComponent динамически, потому что он перечислен по типу в @NgModule.bootstrap или загружен принудительно методом ngDoBootstrap модуля.
Компоненты в определениях маршрутов также являются компонентами входа. Определение маршрута ссылается на компонент по его типу. Маршрутизатор игнорирует селектор маршрутизируемого компонента (если он есть) и загружает компонент динамически в RouterOutlet.
Компилятор не может обнаружить эти компоненты входа, посмотрев их в других шаблонах компонентов. Вам нужно сообщить об этом, добавив их в список entryComponents.
Angular автоматически добавляет следующие типы компонентов в список entryComponents модуля:
- Компонент в списке
@NgModule.bootstrap. - Компоненты, на которые ссылаются в конфигурации маршрутизатора.
Вам не нужно явно указывать эти компоненты, хотя это не повлияет на работу.
В чём разница между компонентом bootstrap и компонентом entry component?
Компонент bootstrap — это компонент entry, который Angular загружает в DOM во время процесса bootstrap (запуска приложения). Другие компоненты entry загружаются динамически другими способами, например, с помощью маршрутизатора.
Свойство @NgModule.bootstrap сообщает компилятору, что это компонент entry, и что он должен сгенерировать код для запуска приложения с этим компонентом.
Нет необходимости перечислять компонент как в списке bootstrap, так и в списке entryComponent, хотя это не повлияет на работу.
Когда добавлять компоненты в entryComponents?
Большинству разработчиков приложений не нужно добавлять компоненты в entryComponents.
Angular автоматически добавляет определённые компоненты в entry components. Компоненты, перечисленные в @NgModule.bootstrap, добавляются автоматически. Компоненты, на которые ссылается конфигурация маршрутизатора, добавляются автоматически. Эти два механизма учитывают почти все компоненты entry.
Если ваше приложение запускает или динамически загружает компонент по типу каким-либо другим способом, вы должны явно добавить его в entryComponents.
Хотя добавление компонентов в этот список не навредит, лучше добавлять только те компоненты, которые действительно являются компонентами entry. Не включайте компоненты, которые ссылаются в шаблонах других компонентов.
Зачем Angular нужны entryComponents?
Компоненты entry также объявляются. Почему компилятор Angular не генерирует код для каждого компонента в @NgModule.declarations? Тогда компоненты entry не понадобились бы.
Причина в tree shaking. Для приложений в производстве вы хотите загрузить максимально быстрый и минимальный код. Код должен содержать только те классы, которые вам действительно нужны. Он должен исключать компонент, который никогда не используется, независимо от того, объявлен он или нет.
На самом деле, многие библиотеки объявляют и экспортируют компоненты, которые вы никогда не будете использовать. Если вы не ссылаетесь на них, tree shaker исключает эти компоненты из конечного пакета кода.
Если бы компилятор Angular генерировал код для каждого объявленного компонента, это уничтожило бы смысл tree shaker.
Вместо этого компилятор использует рекурсивную стратегию, которая генерирует код только для используемых компонентов.
Компилятор начинает с компонентов entry, затем генерирует код для объявленных компонентов, которые он находит в шаблоне компонента entry, затем для объявленных компонентов, обнаруженных в шаблонах ранее скомпилированных компонентов и так далее. В конце этого процесса компилятор сгенерировал код для каждого компонента entry и каждого компонента, доступного из компонента entry.
Если компонент не является компонентом entry или не был найден в шаблоне, компилятор его пропускает.
Какие модули мне следует использовать и как?
Каждое приложение уникально. Разработчики имеют различный опыт и комфорт с доступными вариантами. Некоторые рекомендации и руководства кажутся широко распространёнными.
Следующее — предварительное руководство, основанное на раннем опыте использования NgModules в нескольких приложениях. Читайте с соответствующей осторожностью и размышлениями.
SharedModule
Создайте SharedModule с компонентами, директивами и фильтрами, которые вы используете повсюду в приложении. Этот модуль должен состоять целиком из declarations, большинство из которых экспортируются.
Модуль SharedModule может повторно экспортировать другие модули виджетов, такие как CommonModule, FormsModule, и модули с наиболее часто используемыми UI-элементами.
Модуль SharedModule не должен иметь providers по причинам, объяснённым ранее. И ни один из импортированных или повторно экспортированных модулей не должен иметь providers. Если вы отклоняетесь от этого руководства, знайте, что делаете и почему.
Импортируйте SharedModule в ваши функциональные модули, как загруженные при запуске приложения, так и загружаемые позже.
CoreModule
Создайте CoreModule с providers для одиночных сервисов, которые загружаются при запуске приложения.
Импортируйте CoreModule только в корневой AppModule. Никогда не импортируйте CoreModule ни в какой другой модуль.
Рассмотрите возможность создания CoreModule в виде модуля чистого сервиса без declarations.
В этом образце страницы отступается от этого совета, объявляя и экспортируя два компонента, которые используются только внутри корневого
AppComponent, объявленногоAppModule. Тот, кто следует этому руководству строго, объявил бы эти компоненты вAppModuleвместо этого.
Модули функций
Создавайте модули функций вокруг конкретных областей бизнеса приложения, рабочих процессов пользователей и коллекций утилит.
Модули функций, как правило, относятся к одной из следующих категорий:
- Модули функциональности домена.
- Модули функциональности с маршрутизацией.
- Модули маршрутизации.
- Модули функциональности сервисов.
- Модули функциональности виджетов.
Модули в реальном мире часто являются гибридами, целенаправленно отклоняющимися от следующих рекомендаций. Эти рекомендации не являются законами; следуйте им, если у вас нет веских оснований поступить иначе.
| Модуль функциональности | Рекомендации |
|---|---|
| Домен |
Модули функциональности домена обеспечивают пользовательский интерфейс, посвященный конкретной области приложения, например, редактированию клиента или оформлению заказа. Обычно они имеют компонент высшего уровня, который выполняет роль корневого компонента функциональности. Частные, вспомогательные подкомпоненты наследуются от него. Модули функциональности домена в основном состоят из деклараций. Экспортируется только компонент высшего уровня. У модулей функциональности домена редко бывают провайдеры. Когда они есть, срок службы предоставляемых сервисов должен совпадать со сроком службы модуля. Не предоставляйте в модуле функциональности домена сервисы, используемые во всём приложении (синглетоны). Модули функциональности домена обычно импортируются точно один раз более крупным модулем функциональности. Они могут импортироваться корневым
|
| Маршрутизированный |
Модули функциональности с маршрутизацией являются модулями функциональности домена, верхние компоненты которых являются целями маршрутов навигации маршрутизатора. Все модули, загружаемые по требованию, по определению являются модулями функциональности с маршрутизацией. В данном разделе Модули функциональности с маршрутизацией не должны экспортировать ничего. Это необязательно, так как их компоненты никогда не отображаются в шаблоне внешнего компонента. Модуль функциональности с маршрутизацией, загружаемый по требованию, не должен импортироваться ни одним модулем. Это вызовет немедленную загрузку, что противоречит цели ленивой загрузки. Однако модуль функциональности с маршрутизацией, загружаемый сразу, должен быть импортирован другим модулем, чтобы компилятор узнал о его компонентах. Модули функциональности с маршрутизацией редко имеют провайдеры по причинам, объяснённым ранее. Если они есть, срок службы предоставляемых сервисов должен совпадать со сроком службы модуля. Не предоставляйте сервисы, используемые во всём приложении (синглетоны), в модуле функциональности с маршрутизацией или в модуле, который импортирует модуль с маршрутизацией. |
| Маршрутизации |
Модуль маршрутизации предоставляет конфигурацию маршрутизации для другого модуля. Модуль маршрутизации отделяет вопросы маршрутизации от связанного модуля. Модуль маршрутизации обычно выполняет следующие действия:
Имя модуля маршрутизации должно соответствовать имени связанного модуля, используя суффикс "Маршрутизация". Например, Если связанный модуль является корневым Модуль маршрутизации переэкспортирует Модуль маршрутизации не должен иметь собственный Модуль маршрутизации должен импортироваться только связанным модулем.
|
| Сервис |
Модули сервисов предоставляют вспомогательные сервисы, такие как доступ к данным и обмен сообщениями. В идеале они состоят только из провайдеров и не имеют деклараций. Модули сервисов должны импортироваться только корневым Не импортируйте модули сервисов в другие модули функциональности. Если вы отклоняетесь от этого правила, знайте, что вы делаете и почему. |
| Модуль виджетов |
Модуль виджетов предоставляет компоненты, директивы и трубы внешним модулям.
Модуль виджетов должен состоять только из деклараций, большинство из которых экспортируются. В модуле виджетов обычно редко используются провайдеры. Если вы отклоняетесь от этого руководства, вы должны понимать, что делаете и почему. Импортируйте модули виджетов в любой модуль, шаблоны компонентов которого нуждаются в этих виджетах. |
В следующей таблице обобщены ключевые характеристики каждой группы функциональных модулей.
Реальные модули часто являются гибридами, намеренно отклоняющимися от этих рекомендаций.
| Функциональный модуль | Декларации | Провайдеры | Экспорт | Импортируется в | Примеры |
|---|---|---|---|---|---|
| Домен | Да | Редко | Компонент верхнего уровня | Функциональный, AppModule
|
ContactModule (до маршрутизации) |
| Маршрутизированный | Да | Редко | Нет | Никто |
ContactModule, HeroModule, CrisisModule
|
| Маршрутизация | Нет | Да (сторожа) | RouterModule |
Функциональный (для маршрутизации) |
AppRoutingModule, ContactRoutingModule, HeroRoutingModule
|
| Сервис | Нет | Да | Нет | AppModule |
HttpModule, CoreModule
|
| Виджет | Да | Редко | Да | Функциональный |
CommonModule, SharedModule
|
В чём разница между модулями Angular и JavaScript?
Angular и JavaScript — это разные, но взаимодополняющие системы модулей.
В современном JavaScript каждый файл является модулем (см. страницу Модули на сайте Exploring ES6). Внутри каждого файла вы пишете оператор export, чтобы сделать части модуля общедоступными:
export class AppComponent { ... }
Затем вы import часть в другом модуле:
import { AppComponent } from './app.component';
Такого рода модульность является функцией языка JavaScript.
NgModule — это функция Angular.
В Angular NgModule также есть imports и exports, которые служат аналогичной цели.
Вы импортируете другие NgModule, чтобы использовать экспортируемые классы в шаблонах компонентов. Вы экспортируете классы данного NgModule, чтобы они могли быть импортированы и использованы компонентами других модулей.
Классы NgModule отличаются от классов JavaScript-модулей следующим образом:
- NgModule связывает только декларируемые классы. Декларируемые классы — единственные классы, важные для компилятора Angular.
- Вместо определения всех членов класса в одном большом файле (как в JavaScript-модуле), вы перечисляете классы модуля в списке
@NgModule.declarations. - NgModule может экспортировать только декларируемые классы, которые он владеет или импортирует из других модулей. Он не объявляет и не экспортирует другие типы классов.
NgModule также отличается по-другому. В отличие от JavaScript-модулей, NgModule может расширить всю программу с помощью сервисов, добавив провайдеры в список @NgModule.providers.
Предоставленные сервисы не принадлежат модулю и не ограничены объявленными классами. Они доступны везде.
Вот класс NgModule с импортами, экспортами и объявлениями.
@NgModule({
imports: [ CommonModule, FormsModule ],
declarations: [ ContactComponent, HighlightDirective, AwesomePipe ],
exports: [ ContactComponent ],
providers: [ ContactService ]
})
export class ContactModule { }
Конечно, вы используете JavaScript-модули для написания Angular-модулей, как показано в полном файле contact.module.ts:
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 { }
Как Angular находит компоненты, директивы и трубы в шаблоне? Что такое ссылка на шаблон?
Компилятор Angular ищет в шаблонах компонентов другие компоненты, директивы и трубы. При обнаружении это «ссылка на шаблон».
Компилятор Angular находит компонент или директиву в шаблоне, когда может сопоставить селектор этого компонента или директивы с каким-либо HTML в этом шаблоне.
Компилятор находит трубу, если имя трубы появляется в синтаксисе трубы HTML-шаблона.
Angular ищет селекторы и имена труб только для классов, которые объявлены этим модулем или экспортированы модулем, который импортирован этим модулем.
Что такое компилятор Angular?
Компилятор Angular преобразует написанный вами код приложения в высокопроизводительный код JavaScript. @NgModule метаданные играют важную роль в руководстве процессом компиляции.
Написанный вами код не является непосредственно исполняемым. Рассмотрим компоненты. Компоненты имеют шаблоны, которые содержат пользовательские элементы, атрибутивные директивы, объявления связывания Angular и некоторый особый синтаксис, который явно не является стандартным HTML.
Компилятор Angular читает разметку шаблона, объединяет её с кодом соответствующего класса компонента и генерирует фабрики компонентов.
Компонентный фабрик создаёт чистое, 100%-ное представление компонента на JavaScript, которое включает в себя всё, что описано в метаданных @Component: HTML, инструкции привязки и прикреплённые стили.
Поскольку директивы и трубы появляются во шаблонах компонентов, компилятор Angular также включает их в скомпилированный код компонента.
Метаданные @NgModule сообщают компилятору Angular, какие компоненты следует скомпилировать для этого модуля и как связать этот модуль с другими модулями.
NgModule API
В следующей таблице обобщаются свойства метаданных NgModule.
| Свойство | Описание |
|---|---|
declarations |
Список декларируемых классов, компонентов, директив и труб, которые принадлежат этому модулю. Эти объявленные классы видны внутри модуля, но не видны компонентам в другом модуле, если они не экспортированы из этого модуля, а другой модуль импортирует его. Компоненты, директивы и трубы должны принадлежать ровно одному модулю. Компилятор выводит ошибку, если вы пытаетесь объявить один и тот же класс в более чем одном модуле. Не переобъявляйте класс, импортированный из другого модуля. |
providers |
Список поставщиков инъекции зависимостей. Angular регистрирует этих поставщиков в корневом инжекторе контекста выполнения модуля. Это корневой инжектор приложения для всех загруженных модулей при запуске приложения. Angular может ввести один из этих сервисов-поставщиков в любой компонент в приложении. Если этот модуль или любой загруженный при запуске модуль предоставляет У лениво загружаемого модуля есть свой собственный подкорневой инжектор, который обычно является непосредственным потомком корневого инжектора приложения. Лениво загруженные сервисы ограничены инжектором лениво загруженного модуля. Если лениво загружаемый модуль также предоставляет Компоненты во внешних модулях по-прежнему получают экземпляр, созданный для корня приложения. |
imports |
Список поддерживающих модулей. В частности, список модулей, экспортированные компоненты, директивы или трубы которых упоминаются в шаблонах компонентов, объявленных в этом модуле. Шаблон компонента может ссылаться на другой компонент, директиву или трубу, когда ссылаемый класс объявлен в этом модуле или класс был импортирован из другого модуля. Компонент может использовать директивы Вы можете импортировать многие стандартные директивы с |
exports |
Список объявлений — классы компонентов, директив и труб — которые импортирующий модуль может использовать. Экспортированные объявления являются общедоступным API модуля. Компонент в другом модуле может ссылаться на этот модуль Объявления по умолчанию являются закрытыми. Если этот модуль не экспортирует Импорт модуля не автоматически повторно экспортирует экспорт импортированного модуля. Модуль 'B' не может использовать Модуль может включить другой модуль в свои Повторный экспорт делает транзитивность модуля явной. Если модуль 'A' повторно экспортирует |
bootstrap |
Список компонентов, которые можно запустить. Обычно в этом списке только один компонент, корневой компонент приложения. Angular может запускаться с несколькими компонентами запуска, каждый со своим местоположением в веб-странице хоста. Компонент запуска автоматически является |
entryComponents |
Список компонентов, которые не ссылаются в доступной шаблоне компонента. Большинство разработчиков никогда не устанавливают это свойство. Компилятор Angular должен знать о каждом компоненте, фактически используемом в приложении. Компилятор может обнаружить большинство компонентов, пройдя по дереву ссылок от одного шаблона компонента к другому. Но всегда есть по крайней мере один компонент, который не ссылается ни в одном шаблоне: корневой компонент, Компоненты с маршрутизацией также являются компонентами входа, так как они также не ссылаются в шаблоне. Маршрутизатор создаёт их и помещает в DOM рядом с Хотя запущенные и маршрутизированные компоненты являются компонентами входа, обычно вам не нужно добавлять их в список Angular автоматически добавляет компоненты в список Это оставляет только следующие источники неудобонаблюдаемых компонентов:
Оба являются расширенными техниками, которые редко используются разработчиками. Если вы один из таких редких, вы должны добавить эти компоненты в список |
© 2010–2017 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://v2.angular.io/docs/ts/latest/cookbook/ngmodule-faq.html