首页 文章

Angular 4是在核心模块单件中提供的服务吗?

提问于
浏览
3

我试图理解角度为4的核心模块和单例服务 . 官方文档(https://angular.io/guide/ngmodule)说明了以下内容:

UserService是一个应用程序范围的单例 . 您不希望每个模块都有自己独立的实例 . 然而,如果SharedModule提供UserService,则会发生这种情况 . CoreModule提供UserService . Angular通过app根注入器注册提供者,使得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) BUT, what if I provide the UserService in another module (e.g in a lazy-loading module) ? This lazy-loaded module has a new instance of the service?

关于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会发生什么?它也提供了吗?

谢谢

3 回答

  • 0

    文档很混乱,尤其是这一行:

    UserService是一个应用程序范围的单例 . 您不希望每个模块都有自己独立的实例 . 然而,如果SharedModule提供UserService,则会发生这种情况 .

    有's no danger of that happening if you don't使用延迟加载的模块 . 我们来看一个例子吧 . 您有导入 B 模块的 A 模块 . 两个模块定义提供者:

    @NgModule({
       providers: {provide: 'b', 'b'}
    })
    export class BModule {}
    
    @NgModule({
       imports: [AModule]
       providers: {provide: 'a', 'a'}
    })
    export class AModule {}
    

    编译器生成模块工厂时会发生什么,这些提供程序将一起创建,并且将创建工厂 only for one module . 以下是它的外观:

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

    您可以看到提供程序已合并 . 现在,如果使用 same 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', []),
            ...,
        ]);
    

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

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

    对于延迟加载的模块,Angular会为它们生成 separate 工厂 . 这意味着它们中定义的提供程序不会合并到主模块注入器中 . 因此,如果延迟加载的模块使用相同的令牌定义提供者,Angular将创建该服务的新实例,即使主模块注入器中已有一个实例也是如此 .

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

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

  • 5

    1)是的 . 这涉及依赖性注入器是 hierarchical 的事实 .

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

    2)是的,将提供 UserService . forRoot 将创建 CoreModuledifferent ,其中"normal" CoreModule将使用您添加的额外属性进行扩展 .

    在大多数情况下, forRoot 将采用模块的"normal"版本并包含providers数组,以确保服务是单例 . 模块的"normal"版本只包含组件,管道或其他非单体元素 .

    ngx-translateTranslateModule 为例(提取相关部分):

    @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

    让我试着总结一下我的想法:

    @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参数提供的 .

    And regarding application-wide singleton:

    为了拥有应用程序范围的单例服务(即使是延迟加载的模块)我可以使用forRoot方法:

    static forRoot(): ModuleWithProviders {
      return {
        ngModule: MyModule,
        providers: [
          MySingletonService
        ]
      };
    
    • forRoot只能在appModule中调用一次(所以为什么按惯例调用方法为"forRoot")

    • 如果模块是在多个模块中导入的,则不提供MySingletonService(因为它仅通过forRoot方法提供)

    BUT

    如果我使用以下特殊构造函数创建CoreModule,它会阻止模块多次加载,因此提供的服务是应用程序范围的单例:

    constructor (@Optional() @SkipSelf() parentModule: CoreModule) {
      if (parentModule) {
        throw new Error(
          'CoreModule is already loaded. Import it in the AppModule only');
      }
    }
    

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

    因此,如果我想要应用程序范围(甚至是懒惰模块)服务,我会看到两个选项:

    • 在appModule中调用了forRoot方法的共享模块

    • 核心模块通常提供特殊构造函数和服务,没有forRoot方法

    任何意见?

相关问题