Angular 4 - это услуги, предоставляемые в одном модуле ядра для реального?
Я пытаюсь понять основные модули и синглтон-сервисы в angular 4. Официальная документация ( https://angular.io/guide/ngmodule) гласит следующее:
UserService - это синглтон всего приложения. Вы не хотите, чтобы у каждого модуля был свой отдельный экземпляр. Однако существует реальная опасность того, что это произойдет, если SharedModule предоставляет UserService.
CoreModule предоставляет UserService. Angular регистрирует этого провайдера с помощью корневого инжектора приложения, делая одноэлементный экземпляр UserService доступным для любого компонента, которому он нужен, независимо от того, загружен ли этот компонент быстро или лениво.
Мы рекомендуем собирать такие одноразовые классы и скрывать их детали в CoreModule. Упрощенный корневой AppModule импортирует CoreModule в качестве организатора всего приложения.
import { CommonModule } from '@angular/common';
import { TitleComponent } from './title.component';
import { UserService } from './user.service';
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
@NgModule({
imports: [ CommonModule ],
declarations: [ TitleComponent ],
exports: [ TitleComponent ],
providers: [ UserService ]
})
export class CoreModule {
constructor (@Optional() @SkipSelf() parentModule: CoreModule) { ... }
}
Поэтому я использую модуль ядра, предоставляющий одноэлементные сервисы, и конструктор
constructor (@Optional() @SkipSelf() parentModule: CoreModule) { ... }
запретить импортировать основной модуль более одного раза.
1) НО, что если я предоставлю UserService в другом модуле (например, в модуле с отложенной загрузкой)? У этого лениво загруженного модуля есть новый экземпляр сервиса?
А по поводу метода forRoot:
@NgModule({
imports: [ CommonModule ],
providers: [ UserService ]
})
export class CoreModule {
}
static forRoot(config: UserServiceConfig): ModuleWithProviders {
return {
ngModule: CoreModule,
providers: [
{provide: UserServiceConfig, useValue: config }
]
};
}
}
2) Если я импортирую CoreModule с помощью CoreModule.forRoot() в AppModule, что произойдет с UserService? Это тоже предусмотрено?
Спасибо
3 ответа
Документация сбивает с толку, особенно эта строка:
UserService - это синглтон всего приложения. Вы не хотите, чтобы у каждого модуля был свой отдельный экземпляр. Однако существует реальная опасность того, что это произойдет, если SharedModule предоставляет UserService.
Нет опасности, если вы не используете лениво загруженные модули. Давайте посмотрим на пример. У тебя есть A
модуль, который импортирует B
модуль. Оба модуля определяют провайдеров:
@NgModule({
providers: {provide: 'b', 'b'}
})
export class BModule {}
@NgModule({
imports: [AModule]
providers: {provide: 'a', 'a'}
})
export class AModule {}
Что происходит, когда компилятор генерирует фабрику модулей, так это то, что он объединяет эти провайдеры, и фабрика создается только для одного модуля. Вот как это будет выглядеть:
var AModuleNgFactory = jit_createNgModuleFactory0(
// reference to the module class
jit_AppModule1,
// array of bootstrap components
[jit_AppComponent2],
function (_l) {
return jit_moduleDef3([
// array of providers
jit_moduleProvideDef4(256, 'b', 'b', []),
jit_moduleProvideDef4(256, 'a', 'a', [])
...,
]);
Вы можете видеть, что поставщики объединены. Теперь, если вы определите два модуля с одним и тем же маркером поставщика, модули будут объединены, и поставщики из модуля, который импортирует другой, переопределят импортированные поставщики модулей:
@NgModule({
providers: {provide: 'a', 'b'}
})
export class BModule {}
@NgModule({
imports: [AModule]
providers: {provide: 'a', 'a'}
})
export class AModule {}
Заводское определение теперь будет выглядеть так:
function (_l) {
return jit_moduleDef3([
// array of providers
jit_moduleProvideDef4(256, 'a', 'a', []),
...,
]);
Поэтому независимо от того, сколько модулей вы импортируете, создается только одна фабрика с объединенными провайдерами. И только один корневой инжектор создан. Инжектор, который создают компоненты, не является "настоящим" инжектором. Проверьте этот ответ, чтобы понять, почему.
У этого лениво загруженного модуля есть новый экземпляр сервиса?
Когда дело доходит до лениво загруженных модулей, Angular создает для них отдельные фабрики. Это означает, что определенные в них провайдеры не объединяются с инжектором основного модуля. Таким образом, если лениво загруженный модуль определяет провайдера с тем же токеном, Angular создаст новый экземпляр этой службы, даже если он уже есть в главном модуле инжектора.
Если я импортирую CoreModule с помощью CoreModule.forRoot() в AppModule
Чтобы понять, что forRoot
делает, см. RouterModule.forRoot(ROUTES) против RouterModule.forChild (ROUTES).
1) да. Это связано с тем, что инжектор зависимости является иерархическим.
Это означает, что каждый модуль имеет набор элементов, которые могут быть внедрены (на уровне модуля), и если один из его элементов требует зависимости, которой нет на уровне модуля, то инжектор зависимости будет искать зависимость в родительский модуль (один модуль, который его импортировал) и так далее, пока он не найдет зависимость или не достигнет корня (app.module), где он выдаст ошибку, если зависимость не может быть разрешена (уровень иерархии).
2) Да, UserService
будет обеспечен. forRoot
создаст другую "версию" CoreModule
, в котором "нормальный" CoreModule расширяется добавлением дополнительных свойств.
В большинстве случаев, forRoot
возьмет "нормальную" версию модуля и включит массив провайдеров, чтобы убедиться, что сервисы одноразовые. "Обычная" версия модуля будет иметь только компоненты, каналы или другие не синглетные элементы.
Возьмите в качестве примера TranslateModule
из ngx-translate (извлечен соответствующий раздел):
@NgModule({
declarations: [
TranslatePipe,
TranslateDirective
],
exports: [
TranslatePipe,
TranslateDirective
]
}) // this defines the normal version of the module
export class TranslateModule {
static forRoot(): ModuleWithProviders { // this kinda tells "module + providers"
return {
ngModule: TranslateModule, // take the normal version
providers: [ // merge this to the providers array of the normal version
TranslateStore,
TranslateService
]
};
}
}
Может быть, этот ресурс может быть полезен в качестве дальнейшего объяснения: https://www.youtube.com/watch?v=8VLYjt81-fE
Позвольте мне попытаться суммировать то, что я думаю, чтобы узнать:
@NgModule({
imports: [ CommonModule ],
providers: [
UserService,
UserServiceConfig
]
})
export class CoreModule {
}
static forRoot(config: UserServiceConfig): ModuleWithProviders {
return {
ngModule: CoreModule,
providers: [
{provide: UserServiceConfig, useValue: config }
]
};
}
}
Когда я импортирую модуль, используя метод forRoot:
- UserService также предоставляется, поскольку (как вы, ребята, объяснили) forRoot создает расширенную версию этого модуля (объединяет службы)
- UserServiceConfig предоставляется с помощью аргумента config метода forRoot.
А что касается синглтона всего приложения:
Для того, чтобы иметь синглтон-сервисы для всего приложения (даже для лениво загруженных модулей), я могу использовать метод forRoot:
static forRoot(): ModuleWithProviders {
return {
ngModule: MyModule,
providers: [
MySingletonService
]
};
- forRoot должен вызываться только один раз в appModule (так почему метод называется "forRoot" по соглашению)
- если модуль импортируется в несколько модулей, MySingletonService не предоставляется (поскольку предоставляется только через метод forRoot)
НО
Если я создаю модуль CoreModule со следующим специальным конструктором, он предотвращает загрузку модуля более одного раза, поэтому предоставляемые службы являются одноуровневыми для всего приложения:
constructor (@Optional() @SkipSelf() parentModule: CoreModule) {
if (parentModule) {
throw new Error(
'CoreModule is already loaded. Import it in the AppModule only');
}
}
Поэтому имеет смысл использовать метод forRoot в SharedModule, а не в модуле со специальным конструктором выше.
Поэтому, если мне нужны сервисы для всего приложения (даже для ленивых модулей), я вижу два варианта:
- Общий модуль с методом forRoot, вызываемым в appModule
- Базовый модуль со специальным конструктором и службами, предоставляемыми нормально, без метода forRoot
Любые комментарии?