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) 如果我在 AppModule 中使用 CoreModule.forRoot() 导入 CoreModule,那么 UserService 会发生什么?是否也提供?

谢谢

4

3 回答 3

5

文档令人困惑,尤其是这一行:

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', [])
            ...,
        ]);

您可以看到提供程序已合并。现在,如果您使用相同的 provider token定义两个模块,则这些模块将被合并,并且导入另一个模块的模块中的提供程序将覆盖导入的模块提供程序:

@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 将创建该服务的新实例,即使主模块注入器中已经有一个实例。

如果我在 AppModule 中使用 CoreModule.forRoot() 导入 CoreModule

要了解forRoot它的作用,请参阅RouterModule.forRoot(ROUTES) 与 RouterModule.forChild(ROUTES)

于 2017-07-21T16:44:39.667 回答
1

让我试着总结一下我认为学到的东西:

@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 是使用 forRoot 方法的 config 参数提供的。

关于应用程序范围的单例:

为了拥有应用程序范围的单例服务(即使对于延迟加载的模块),我可以使用 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');
  }
}

因此,在 SharedModule 中使用 forRoot 方法是有意义的,而不是在具有上述特殊构造函数的模块中。

因此,如果我想要应用程序范围的(即使对于惰性模块)服务,我会看到两个选项:

  1. 在 appModule 中调用 forRoot 方法的共享模块
  2. 具有特殊构造函数和正常提供服务的核心模块,没有 forRoot 方法

任何意见?

于 2017-07-24T09:34:25.600 回答
1

1) 是的。这与依赖注入器是分层的这一事实有关。

这意味着,每个模块都有一组可以注入的元素(模块级别),如果其中一个元素需要在模块级别不存在的依赖项,那么依赖注入器将在模块的父模块(导入它的一个模块),依此类推,直到找到依赖项或到达根(app.module),如果依赖项无法解决(层次结构级别),它将抛出错误。

2) 是的,UserService将提供。forRoot将创建一个不同的“版本” CoreModule,其中“普通”CoreModule 使用您添加的额外属性进行扩展。

在大多数情况下,forRoot将采用模块的“正常”版本并包含 providers 数组,以确保服务是单例的。模块的“普通”版本将只有组件、管道或其他非单调元素。

TranslateModulengx-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

于 2017-07-21T10:08:11.093 回答