Прим. перев.: для понимания данной статьи необходимо обладать начальными знаниями Angular: что такое компоненты, как создать простейшее SPA приложение и т.д. Если Вы не знакомы с данной темой, то рекомендую для начала ознакомиться с примером создания SPA приложения из оф. документации.
Вступление
@NgModule
— декоратор, добавленный в Angular 2. Из официальной документации следует, что @NgModule
определяет класс, как модуль Angular. Модули Angular помогают разбивать приложение на части (модули), которые взаимодействуют между собой и представляют в конечном итоге целостное приложение. Иными словами, модуль — это упаковка или инкапсуляция части функционала приложения. Модули можно проектировать с учетом многократного использования, т.е. не зависящие от конкретной реализации приложения.
Обратите внимание, что если вы хотите реализовать lazy load в Вашем приложении, то необходимо использовать концепцию Angular Modules при проектировании приложения.
Корневой модуль (Root Module)
Корневой модуль в приложении Angular используется в качестве точки входа. Модуль — это класс, который декорирован при помощи @NgModule
. Взглянем на стандартный код модуля (например, при создании нового проекта через ng new project-name
генерируется такой AppModule
:
// app.module.ts
@NgModule({
imports: [BrowserModule],
declarations: [AppComponent],
bootstrap: [AppComponent],
})
export class AppModule { }
В качестве аргумента в декораторе @NgModule
используется JavaScript объект.
В свойстве declarations
мы объявляем компоненты, которые содержит наш модуль. В данном случае это компонент AppComponent
. Компонентов может быть несколько, они объявляются через запятую (как в обычном JavaScript массиве).
declarations: [AppComponent]
Предположим, что наш компонент имеет селектор my-app
. Когда в шаблоне HTML мы пишем <my-app></my-app>
приложение загружает компонент.
Как происходит загрузка компонента в DOM?
AppModule
сообщает браузеру, что необходимо встроить в DOM компонент AppComponent
.
Каким образом AppModule
добавляет компонент в DOM?
AppModule
импортирует служебные модули Angular, например
BrowserModule
, отвечающий за работу приложения в браузере. По сути это сервис, который взаимодействует с нашим кодом (например AppComponent
) и API браузера. BrowserModule
также включает в себя директивы NgIf
и NgFor
, который мы можем использовать в шаблонах компонентов.
AppModule
выступает в роли связующего между данными, их представлением и браузером.
Компоновка приложения
Использование приложение Angular начинается с файла main.ts
, более подробно читайте мануал.
// main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
В примере выше мы используем динамическую компиляцию (JIT — Just In Time). Однако в Angular можно реализовать AOT-компиляцию (Ahead of Time), однако это обширная тема и будет рассмотрена в другой статье.
Декларирование (объявление)
В наших приложения мы создаем свои компоненты, директивы и пайпы. Мы можем сообщить нашему приложению о том, какой функционал мы хотим добавить (компоненты, директивы и пайпы) путем перечисления их в свойстве declarations
объекта, который является аргументом для декоратора @NgModule
:
// app.module.ts
@NgModule({
imports: [BrowserModule],
declarations: [
AppComponent,
VehicleListComponent,
VehicleSelectionDirective,
VehicleSortingPipe
],
bootstrap: [AppComponent],
})
export class AppModule { }
После объявления компонентов мы можем использовать их внутри других компонентов через их селектор, который указывается в описании компонента.
Импортирование и вложенные модули
Мы импортируем BrowserModule
в AppModule
, который в свою очередь импортирует CommonModule
, который и содержит директивы NgIf
и NgFor
, доступные в Angular из коробки. Импортируя CommonModule
мы получаем доступ к функционалу, реализованный в данном модуле во всем приложении.
Но что если мы хотим использовать функционал, который необходим для реализации форм, например ngModel
и http
? Решение просто — нам достаточно импортировать данные модули в AppModule
:
// app.module.ts
@NgModule({
imports: [
BrowserModule,
FormsModule,
HttpModule
],
declarations: [
AppComponent,
VehicleListComponent,
VehicleSelectionDirective,
VehicleSortingPipe
],
bootstrap: [AppComponent],
})
export class AppModule { }
После того, как мы импортировали модуль FormsModule
, мы можем использовать ngModel
в шаблонах наших компонентов. А HttpModule
позволяет нам отправлять или получать данные по HTTP протоколу.
Провайдеры
Часто в наших приложениях есть сервисы, которые мы хотим использовать в нескольких (или во всех) компонентах. Как предоставить всем компонентам доступ к сервису? Использовать Angular Modules.
Для этого необходимо добавить провайдер в корневой модуль, в нашем случае AppModule
. В Angular добавление зависимостей реализовано при помощи паттерна Dependency Incetion. Когда мы добавили провайдер в корневой модуль AppModule
, он будет доступен в любой части приложения.
В качестве наиболее наглядного примера провайдера, который нужен в нашем приложении везде — сервис авторизации (Logger Service). Еще наше приложение должно показывать список активных пользователей, поэтому нам необходим VehicleService
для извлечения списка активных пользователей, обращаясь к API, используя http
, который мы можем использовать благодаря модулю HttpModule
. Его мы импортировали в наше приложение раньше.
// logger.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class LoggerService {
log = (msg: string) => {
console.log(msg);
};
}
// vehicle.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class VehicleService {
getVehicles = () => [
{ id: 1, name: 'X-Wing Fighter' },
{ id: 2, name: 'Tie Fighter' },
{ id: 3, name: 'Y-Wing Fighter' }
];
}
Когда мы представили эти сервисы в корневом модуле AppModule
они доступны в компонентах приложения:
// app.module.ts
@NgModule({
imports: [BrowserModule, FormsModule, HttpModule],
declarations: [
AppComponent,
VehicleListComponent,
VehicleSelectionDirective,
VehicleSortingPipe
],
providers: [
LoggerService,
VehicleService,
UserProfileService
],
bootstrap: [AppComponent],
})
export class AppModule { }
Опишем, как это работает. Мы добавили в конструктор нашего компонента создание объекта провайдера, выглядит это примерно так:
// some.component.ts
@Component({
selector: 'some-component',
template: '<h1>Some component {{ service.data }}</h1>'
styleUrls: ['./path/to/style.css']
})
export class SomeComponent implements OnInit{
constructor (private service: Service){} //Создание объекта сервиса
// Основной код компонента
}
При создании экземпляра компонента (например, когда мы открыли страницу, где используется компонент) одновременно создается объект сервиса, описанный в свойстве providers
(см. листинг app.module.ts выше) и Angular начинает искать класс этого сервиса в родительских компонентах. Т.к. мы не объявляли провайдер в компонентах, в конечном итоге Angular найдет этот сервис в корневом модуле AppModule
. Следует отметить, что провайдер в Angular реализуют паттерн Singleton, так что в приложении может быть создан только один экземпляр класса сервиса. В нашем случае, если экземпляр сервиса уже был где-то создан, то компонент будет использовать его, если нет, то создаст объект сервиса.
Заключение
Провайдеры, на мой взгляд, одна из самых интересных и сложных концепций модульной системы Angular. Основное правило использования провайдеров (сервисов) — создать экземпляр сервиса в корневом модуле и передавать по запросу этот экземпляр в другие части (компоненты) приложения.
— Telegram русскоязычного Angular сообщества.