Перейти к содержанию

FAQ по NgModule

📅 28.02.2022

Модули NgModules помогают организовать приложение в целостные функциональные блоки.

Эта страница отвечает на вопросы, которые задают многие разработчики о проектировании и реализации NgModule.

Какие классы следует добавить в массив declarations?

Добавьте декларируемые классы —компоненты, директивы и пайпы — в список declarations.

Объявите эти классы в только одном модуле приложения. Объявляйте их в модуле, если они принадлежат именно этому модулю.

Что такое декларируемый?

Declarables — это типы классов — компоненты, директивы и пайпы — которые можно добавить в список declarations модуля. Это единственные классы, которые можно добавлять в declarations.

Какие классы не следует не добавлять в declarations?

Добавляйте в список declarations модуля NgModule только декларируемые классы.

Не следует не объявлять следующее:

  • Класс, который уже объявлен в другом модуле, будь то модуль приложения, @NgModule или модуль стороннего разработчика.
  • Массив директив, импортированных из другого модуля. Например, не объявляйте FORMS_DIRECTIVES из @angular/forms, так как в модуле FormsModule он уже объявлен.

  • Классы модулей.

  • Сервисные классы.
  • Неангулярные классы и объекты, такие как строки, числа, функции, модели сущностей, конфигурации, бизнес-логика и вспомогательные классы.

Зачем указывать один и тот же компонент в нескольких свойствах NgModule?

Компонент AppComponent часто указывается в свойствах declarations и bootstrap. Вы можете увидеть один и тот же компонент в свойствах declarations и exports.

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

  • AppComponent может быть объявлен в этом модуле, но не загружен.
  • AppComponent может быть загружен в этом модуле, но объявлен в другом функциональном модуле.
  • Компонент может быть импортирован из другого прикладного модуля (поэтому его нельзя объявить) и реэкспортирован этим модулем.
  • Компонент может быть экспортирован для включения в шаблон внешнего компонента, а также динамически загружаться во всплывающем диалоге.

Что означает ошибка "Невозможно привязать к 'x', так как это не является известным свойством 'y'"?

Эта ошибка часто означает, что вы не объявили директиву "x" или не импортировали модуль NgModule, к которому относится "x".

Возможно, вы объявили "x" в подмодуле приложения, но забыли его экспортировать. Класс "x" не будет виден другим модулям, пока вы не добавите его в список exports.

Что нужно импортировать?

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

Это всегда означает импорт CommonModule из @angular/common для доступа к директивам Angular, таким как NgIf и NgFor. Вы можете импортировать его напрямую или из другого NgModule, который реэкспортирует его.

Импортируйте FormsModule из @angular/forms, если ваши компоненты имеют выражения двустороннего связывания [(ngModel)].

Импортируйте модули shared и feature, если компоненты этого модуля включают их компоненты, директивы и пайпы.

Импортируйте BrowserModule только в корневой AppModule.

Что импортировать: BrowserModule или CommonModule?

Корневой модуль приложения, AppModule, практически любого браузерного приложения должен импортировать BrowserModule из @angular/platform-browser.

BrowserModule предоставляет сервисы, необходимые для запуска и работы браузерного приложения.

BrowserModule также реэкспортирует CommonModule из @angular/common, что означает, что компоненты в AppModule также имеют доступ к директивам Angular, необходимым каждому приложению, таким как NgIf и NgFor.

Не импортируйте BrowserModule в любой другой модуль. Модули Feature и lazy-loaded должны импортировать CommonModule вместо этого. Им нужны общие директивы. Им не нужно переустанавливать общеприкладные провайдеры.

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

Что делать, если я импортирую один и тот же модуль дважды?

Это не проблема. Когда три модуля импортируют модуль 'A', Angular оценивает модуль 'A' один раз, когда встречает его в первый раз, и больше не делает этого.

Это справедливо для любого уровня A в иерархии импортируемых NgModules. Если модуль 'B' импортирует модуль 'A', модуль 'C' импортирует 'B', а модуль 'D' импортирует [C, B, A], то 'D' вызывает оценку 'C', которая вызывает оценку 'B', которая оценивает 'A'. Когда Angular добирается до 'B' и 'A' в 'D', они уже кэшированы и готовы к работе.

Angular не любит NgModules с круговыми ссылками, поэтому не позволяйте модулю 'A' импортировать модуль 'B', который импортирует модуль 'A'.

Что нужно экспортировать?

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

Вы можете экспортировать любой декларируемый класс — компоненты, директивы и пайпы— независимо от того, объявлен ли он в данном NgModule или в импортированном NgModule.

Вы можете реэкспортировать целые импортированные NgModules, которые фактически реэкспортируют все экспортированные классы. NgModule может даже экспортировать модуль, который он не импортирует.

Что не следует экспортировать?

Не экспортируйте следующее:

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

  • Недекларируемые объекты, такие как сервисы, функции, конфигурации и модели сущностей.

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

  • Чистые сервисные модули, не имеющие публичных (экспортируемых) деклараций. Например, нет смысла реэкспортировать HttpClientModule, поскольку он ничего не экспортирует. Его единственное назначение — добавление провайдеров http-сервисов в приложение в целом.

Можно ли реэкспортировать классы и модули?

Безусловно.

NgModules — это отличный способ выборочно объединить классы из других NgModules и реэкспортировать их в консолидированный, удобный модуль.

Модуль NgModule может реэкспортировать целые модули NgModules, которые фактически реэкспортируют все экспортированные классы. Собственный модуль Angular BrowserModule экспортирует несколько NgModules подобным образом:

1
exports: [CommonModule, ApplicationModule];

Модуль NgModule может экспортировать комбинацию своих собственных деклараций, выбранных импортированных классов и импортированных модулей NgModules.

Не стоит заниматься реэкспортом чистых сервисных модулей. Чистые сервисные модули не экспортируют декларируемые классы, которые может использовать другой NgModule. Например, нет смысла реэкспортировать HttpClientModule, поскольку он ничего не экспортирует. Его единственное назначение — добавление провайдеров http-сервисов в приложение в целом.

Что такое метод forRoot()?

Статический метод forRoot() — это соглашение, облегчающее разработчикам конфигурирование сервисов и провайдеров, которые должны быть синглтонами. Хорошим примером метода forRoot() является метод RouterModule.forRoot().

Приложения передают массив Routes в RouterModule.forRoot(), чтобы сконфигурировать общеприкладной сервис Router с маршрутами. RouterModule.forRoot() возвращает ModuleWithProviders. Вы добавляете этот результат в список импортов корневого AppModule.

Вызывайте и импортируйте результат forRoot() только в корневом модуле приложения, AppModule. Избегайте импортировать его в любой другой модуль, особенно в модуль с ленивой загрузкой. Более подробную информацию о forRoot() см. в разделе паттерн forRoot() руководства Singleton Services.

Импорт forRoot() может быть использован в модуле, отличном от AppModule. Важно отметить, что forRoot() должен быть вызван только один раз, а модуль, импортирующий forRoot(), должен быть доступен корневому ModuleInjector. Для получения дополнительной информации обратитесь к руководству по Иерархическим инжекторам.

Для сервиса вместо использования forRoot() следует указать providedIn: 'root' в декораторе сервиса @Injectable(), что делает сервис автоматически доступным для всего приложения и, таким образом, синглтоном по умолчанию.

RouterModule также предлагает статический метод forChild() для конфигурирования маршрутов лениво загружаемых модулей.

forRoot() и forChild() — это условные названия методов, конфигурирующих сервисы в корневых и функциональных модулях соответственно.

При написании аналогичных модулей с настраиваемыми поставщиками услуг следуйте этому соглашению.

Почему сервис, предоставляемый в функциональном модуле, виден везде?

Провайдеры, перечисленные в @NgModule.providers загружаемого модуля, имеют область видимости приложения. Добавление провайдера в @NgModule.providers фактически публикует сервис для всего приложения.

При импорте NgModule Angular добавляет провайдеров сервиса модуля (содержимое списка providers) в инжектор корня приложения.

Это делает провайдера видимым для каждого класса в приложении, который знает маркер поиска провайдера, или его имя.

Расширяемость за счет импорта NgModule является основной целью системы NgModule. Включение провайдеров NgModule в инжектор приложения позволяет библиотеке модулей легко обогатить все приложение новыми сервисами. Добавив один раз модуль HttpClientModule, каждый компонент приложения сможет выполнять HTTP-запросы.

Однако это может показаться нежелательным сюрпризом, если вы ожидаете, что сервисы модуля будут видны только компонентам, объявленным этим функциональным модулем. Если модуль HeroModule предоставляет сервис HeroService, а корневой модуль AppModule импортирует HeroModule, то любой класс, знающий тип сервиса HeroService, может использовать этот сервис, а не только классы, объявленные в модуле HeroModule.

Чтобы ограничить доступ к сервису, рассмотрите возможность ленивой загрузки модуля NgModule, предоставляющего этот сервис. Дополнительную информацию см. в разделе How do I restrict service scope to a module?.

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

В отличие от провайдеров модулей, загружаемых при запуске, провайдеры лениво загружаемых модулей являются модульно-скопированными.

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

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

Эти провайдеры изолированы от изменений провайдеров приложений с тем же маркером поиска. Когда маршрутизатор создает компонент в контексте lazy-loaded, Angular предпочитает экземпляры сервисов, созданные на основе этих провайдеров, экземплярам сервисов корневого инжектора приложения.

Что делать, если два модуля предоставляют один и тот же сервис?

Если в двух одновременно загруженных импортируемых модулях указан провайдер с одинаковым токеном, то "побеждает" провайдер второго модуля. Это происходит потому, что оба провайдера добавлены в один и тот же инжектор.

Когда Angular ищет возможность инжектировать сервис для этого токена, он создает и доставляет экземпляр, созданный вторым провайдером.

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

Если модуль NgModule A предоставляет сервис для маркера 'X' и импортирует модуль NgModule B, который также предоставляет сервис для маркера 'X', то определение сервиса модуля NgModule A "побеждает".

Сервис, предоставляемый корневым AppModule, имеет приоритет над сервисами, предоставляемыми импортированными NgModules. Всегда побеждает AppModule.

Как ограничить область действия сервиса в модуле?

Когда модуль загружается при запуске приложения, его @NgModule.providers имеют обширную область применения, то есть они доступны для инъекции во всем приложении.

Импортированные провайдеры легко заменяются провайдерами из другого импортированного NgModule. Такая замена может быть запланирована. Она может быть непреднамеренной и иметь неблагоприятные последствия.

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

Допустим, модуль требует настроить HttpBackend, который добавляет специальный заголовок для всех Http-запросов. Если другой модуль в другом месте приложения также настраивает HttpBackend или просто импортирует HttpClientModule, он может переопределить провайдер HttpBackend этого модуля, потеряв специальный заголовок. Сервер будет отклонять http-запросы от этого модуля.

Чтобы избежать этой проблемы, импортируйте HttpClientModule только в AppModule, корневом модуле приложения.

Если необходимо защититься от подобного "повреждения провайдера", не полагайтесь на провайдеры запускаемого модуля.

Если есть возможность, загрузите модуль лениво. Angular предоставляет lazy-loaded module свой собственный дочерний инжектор. Провайдеры модуля видны только в дереве компонентов, созданных с помощью этого инжектора.

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

Продолжая тот же пример, предположим, что компонентам модуля действительно требуется частный, пользовательский HttpBackend.

Создайте "верхний компонент", который будет являться корневым для всех компонентов модуля. Добавьте пользовательский провайдер HttpBackend в список providers верхнего компонента, а не в список providers модуля. Напомним, что Angular создает дочерний инжектор для каждого экземпляра компонента и заполняет его собственными провайдерами компонента.

Когда дочерний компонент запрашивает сервис HttpBackend, Angular предоставляет локальный сервис HttpBackend, а не версию, указанную в инжекторе корня приложения. Дочерние компоненты выполняют корректные HTTP-запросы независимо от того, что другие модули делают с HttpBackend.

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

Дочерние компоненты можно встраивать в шаблон верхнего компонента. В качестве альтернативы можно сделать верхний компонент узлом маршрутизации, присвоив ему <router-outlet>. Определите дочерние маршруты и позвольте маршрутизатору загружать компоненты модуля в этот аутлет.

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

Следует ли добавлять провайдеров для всего приложения в корневой AppModule или в корневой AppComponent?

Определите провайдеров для всего приложения, указав providedIn: 'root' в его декораторе @Injectable() (в случае сервисов) или при конструировании InjectionToken (в случае предоставления токенов). Провайдеры, созданные таким образом, автоматически становятся доступны всему приложению и не нуждаются в указании в каком-либо модуле.

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

Лениво загруженные модули и их компоненты могут инжектировать сервисы AppModule; они не могут инжектировать сервисы AppComponent.

Регистрируйте сервис в провайдерах AppComponent только_ в том случае, если сервис должен быть скрыт от компонентов вне дерева AppComponent. Это редкий случай использования.

В общем случае предпочтительнее регистрировать провайдеров в NgModules, чем в компонентах.

Обсуждение

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

Некоторые сервисы, например Router, работают только при регистрации их в инжекторе корня приложения.

В отличие от этого, Angular регистрирует провайдеров AppComponent в собственном инжекторе AppComponent. Сервисы AppComponent доступны только для этого компонента и его дерева компонентов. Они имеют область видимости компонента.

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

Нужно ли добавлять другие провайдеры в модуль или компонент?

Провайдеры должны быть сконфигурированы с использованием синтаксиса @Injectable. По возможности их следует предоставлять в корне приложения (providedIn: 'root'). Сервисы, сконфигурированные таким образом, считаются lazily loaded, если они используются только из lazily loaded контекста.

Если потребитель решает, доступен ли провайдер в масштабах всего приложения или нет, то регистрируйте провайдеров в модулях (@NgModule.providers), а не в компонентах (@Component.providers).

Регистрируйте провайдера в компоненте, если вы должны ограничить область действия экземпляра сервиса только этим компонентом и его деревом компонентов. Аналогичные рассуждения применимы и к регистрации провайдера в директиве.

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

Всегда регистрируйте сервисы в масштабах приложения в корневом AppModule, а не в корневом AppComponent.

Почему плохо, если разделяемый модуль предоставляет сервис лениво загружаемому модулю?

Сценарий с нетерпеливой загрузкой

Когда загружаемый с нетерпением модуль предоставляет сервис, например UserService, этот сервис доступен во всем приложении. Если корневой модуль предоставляет UserService и импортирует другой модуль, предоставляющий тот же UserService, Angular регистрирует один из них в инжекторе корневого приложения (см. What if I import the same module twice?).

Затем, когда какой-либо компонент инжектирует UserService, Angular находит его в инжекторе корня приложения и доставляет синглтонный сервис в масштабах всего приложения. Никаких проблем.

Сценарий с ленивой загрузкой

Теперь рассмотрим лениво загружаемый модуль, который также предоставляет сервис под названием UserService.

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

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

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

Почему при ленивой загрузке создается дочерний инжектор?

Angular добавляет @NgModule.providers в инжектор корня приложения, если только NgModule не загружен лениво. Для лениво загружаемого NgModule Angular создает дочерний инжектор и добавляет провайдеры модуля в дочерний инжектор.

Это означает, что NgModule ведет себя по-разному в зависимости от того, загружен ли он при старте приложения или лениво загружен позже. Пренебрежение этим различием может привести к неблагоприятным последствиям.

Почему Angular не добавляет лениво загружаемые провайдеры в инжектор корня приложения, как это делается для нетерпеливо загружаемых NgModules?

Ответ кроется в фундаментальной характеристике системы инжекции зависимостей Angular. Инжектор может добавлять провайдеров до тех пор, пока он не будет впервые использован. Как только инжектор начинает создавать и предоставлять сервисы, его список провайдеров замораживается; добавление новых провайдеров не допускается.

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

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

Как определить, был ли ранее загружен NgModule или сервис?

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

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

Некоторые модули NgModules, например BrowserModule, реализуют подобную защиту. Здесь приведен пользовательский конструктор для NgModule под названием GreetingModule.

1
2
3
4
5
6
constructor(@Optional() @SkipSelf() parentModule?: GreetingModule) {
  if (parentModule) {
    throw new Error(
      'GreetingModule is already loaded. Import it in the AppModule only');
  }
}

Какие типы модулей должны быть у меня и как их использовать?

Каждое приложение отличается от другого. Разработчики имеют различный уровень опыта и комфорта при работе с имеющимися вариантами. Некоторые предложения и рекомендации, как представляется, имеют широкую популярность.

SharedModule

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

Модуль SharedModule может реэкспортировать другие модули виджетов, такие как CommonModule, FormsModule и NgModules с элементами управления пользовательским интерфейсом, которые вы используете наиболее широко.

По причинам объясненным ранее SharedModule не должен иметь providers. Также ни один из его импортируемых или реэкспортируемых модулей не должен иметь providers.

Импортируйте SharedModule в свои функциональные модули, как те, которые загружаются при старте приложения, так и те, которые вы лениво загружаете позже.

Feature Modules

Модули Feature — это модули, создаваемые для определенных бизнес-доменов приложения, рабочих процессов пользователей и коллекций утилит. Они поддерживают работу приложения и содержат определенные функции, такие как маршруты, сервисы, виджеты и т.д. Чтобы представить себе, что может представлять собой функциональный модуль в вашем приложении, подумайте, что если вы поместите файлы, относящиеся к определенной функциональности, например поиску, в одну папку, то содержимое этой папки будет представлять собой функциональный модуль, который вы можете назвать SearchModule. Он будет содержать все компоненты, маршрутизацию и шаблоны, составляющие функциональность поиска.

Более подробную информацию можно найти в разделах Feature Modules и Module Types

В чем разница между NgModules и JavaScript Modules?

В Angular-приложении NgModules и JavaScript-модули работают вместе.

В современном JavaScript каждый файл является модулем (см. страницу Modules на сайте Exploring ES6). Внутри каждого файла вы пишете оператор export, чтобы сделать части модуля общедоступными.

Angular NgModule — это класс с декоратором @NgModule — JavaScript-модули не обязательно должны иметь декоратор @NgModule. В Angular у NgModule есть imports и exports, и они служат аналогичной цели.

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

Для получения дополнительной информации см. раздел JavaScript Modules vs. NgModules.

Как Angular находит компоненты, директивы и пайпы в шаблоне? Что такое ссылка на шаблон?

Компилятор Angular compiler ищет в шаблонах компонентов другие компоненты, директивы и пайпы. Если он находит таковые, это и есть ссылка на шаблон.

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

Компилятор находит пайп, если его имя встречается в синтаксисе пайпа в HTML шаблона.

Angular сопоставляет селекторы и имена пайпов только для классов, объявленных этим модулем или экспортированных модулем, который этот модуль импортирует.

Что такое компилятор Angular?

Компилятор Angular преобразует написанный вами код приложения в высокопроизводительный JavaScript-код. Метаданные @NgModule играют важную роль в управлении процессом компиляции.

Написанный вами код не является сразу исполняемым. Например, в компонентах есть шаблоны, содержащие пользовательские элементы, директивы атрибутов, объявления привязок Angular, а также своеобразный синтаксис, который явно не является родным HTML.

Компилятор Angular считывает разметку шаблона, комбинирует ее с кодом соответствующего класса компонента и создает фабрики компонентов.

Фабрика компонентов создает чистое, 100% JavaScript-представление компонента, включающее в себя все, что описано в метаданных @Component: HTML, инструкции по связыванию, присоединенные стили.

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

Метаданные @NgModule указывают компилятору Angular, какие компоненты компилировать для данного модуля и как связать этот модуль с другими модулями.

Ссылки

Комментарии