【问题标题】:Angular 4 are services provided in a core module singleton for real?Angular 4 是真正在核心模块单例中提供的服务吗?
【发布时间】:2017-07-21 09:58:24
【问题描述】:

我正在尝试了解 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 会发生什么?也提供了吗?

谢谢

【问题讨论】:

  • 嘿,my answer 有帮助吗?
  • 是的,你们都帮助我更好地理解了发生了什么......我仍然需要看看你提供的链接。我仍然很困惑何时/为什么应该将 Core Module 与 forRoot 方法和延迟加载模块一起使用,以及 coreModule 中提供的服务是否真的是应用程序范围的单例,或者是使它们成为单例的 forRoot 方法
  • 好的,阅读我提供的链接,然后提出澄清问题
  • 嘿,my answer有什么不清楚的地方吗?

标签: angular ng-modules


【解决方案1】:

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

UserService 是一个应用程序范围的单例。你不想每个 模块拥有自己的单独实例。 但确实存在危险 如果 SharedModule 提供 UserService 会发生这种情况。

如果您不使用延迟加载模块,则不会发生这种情况。让我们看一个例子。您有导入 B 模块的 A 模块。两个模块都定义了提供者:

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

因此,无论您导入多少模块,都只会创建一个具有合并提供程序的工厂。并且只创建了一个根注入器。组件创建的注入器不是“真正的”注入器 - 检查this answer 以了解原因。

这个延迟加载的模块有一个新的服务实例?

当涉及到延迟加载的模块时,Angular 会为它们生成单独的工厂。这意味着其中定义的提供程序不会合并到主模块注入器中。因此,如果一个延迟加载的模块使用相同的令牌定义了提供者,Angular 将创建该服务的新实例,即使主模块注入器中已经有一个。

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

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

【讨论】:

  • 我在 gitter 的 angular room 上问过,你是对的!只有延迟加载的模块有自己的注入器,它们是来自根的子注入器。
  • @Jota.Toledo,太好了,也许你可以支持我的回答)
【解决方案2】:

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

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

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

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

以ngx-translate的TranslateModule为例(摘录相关部分):

@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

【讨论】:

  • 谢谢,所以对于第 1 点)开发人员应该关心它,没有安全的 100% 方法来拥有服务的单个实例 2)如果我使用 forRoot 方法,则在@NgModule({..}) 部分没用
  • 1) 是的,正如我所提到的,创建提供者的模块级实例非常容易,这些实例将被注入而不是分层实例。 2) 是的,在大多数情况下,您会将提供程序添加到 fororRoot 方法中。我会用进一步的解释来更新我的答案
  • 更正了我对你第二点的回答,我之前错过了一些东西:)
  • forRoot 不做任何事情,它与使用提供者定义模块相同
  • 什么都不做是什么意思?确实如此,它通过包装现有模块并向其添加提供程序来创建一个新模块
【解决方案3】:

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

@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 方法

有没有cmets?

【讨论】:

    猜你喜欢
    • 2017-02-14
    • 1970-01-01
    • 1970-01-01
    • 2018-03-27
    • 2019-09-09
    • 2020-01-15
    • 2020-10-14
    • 2014-09-11
    • 1970-01-01
    相关资源
    最近更新 更多