【问题标题】:Angular: Lazy loading modules with servicesAngular:使用服务延迟加载模块
【发布时间】:2018-06-19 14:10:55
【问题描述】:

我一直在关注this 教程,了解延迟加载,以下是我的推论。

场景 1:通过将服务放在子模块的 providers 数组中来提供服务

场景 2:使用forRoot 方法在子模块中提供服务

结合情景 1,

  • 如果一个子模块被预先加载,服务的一个实例会被添加到根注入器中。
  • 如果延迟加载子模块,则会将服务的实例添加到根注入器中,并将新的服务实例添加到子注入器中,这不是通常的用例。

结合情景 2,

  • 如果子模块被急切加载,则服务的实例是 添加到根注入器。

  • 如果子模块被延迟加载,服务的同一个实例 在根模块和子模块中都可用,即 常用用例。

他们提到了以下内容。

一开始,

因此,即使使用模块,也没有办法拥有“私有” 服务,除非...模块被延迟加载。

最后,

虽然这种语法比原来的语法稍微复杂一些,但它 将向我们保证只有一个 CreditCardService 实例是 添加到根模块。当 CreditCardModule 被加载时(甚至 延迟加载),不会将该服务的新实例添加到 子注射器。

如果实例也将在根注入器中可用,他们如何说服务是“私有化”的?

我很困惑。有人请澄清。

【问题讨论】:

    标签: angular angular2-services angular2-modules angular2-providers


    【解决方案1】:

    providedIn: 'root' 是自 Angular 6 以来提供服务的最简单、最有效的方式:

    1. 该服务将在应用程序范围内作为单例提供,无需将其添加到模块的提供程序数组中(如 Angular
    2. 如果服务仅在延迟加载的模块中使用,它将与该模块一起延迟加载
    3. 如果从未使用过,它将不会包含在构建中(摇动树)。

    如需了解更多信息,请考虑阅读documentationNgModule FAQs

    顺便说一句:

    1. 如果您不想要应用程序范围的单例,请改用提供程序的组件数组。
    2. 如果您想限制范围以使其他开发人员永远不会在特定模块之外使用您的服务,请改用提供者的 NgModule 数组。*

    *更新

    '使用NgModule的provider数组'表示使用延迟加载模块的providers数组,例如:

    import { NgModule } from '@angular/core';
    
    import { UserService } from './user.service';
    
    @NgModule({
      providers: [UserService],
    })
    export class UserModule {
    }
    

    或者在可注入装饰器中实际命名模块:

    import { Injectable } from '@angular/core';
    import { UserModule } from './user.module';
    
    @Injectable({
      providedIn: UserModule,
    })
    export class UserService {
    }
    

    引用自文档:

    当路由器在延迟加载的上下文中创建组件时, Angular 更喜欢从这些提供者创建的服务实例,而不是 应用根注入器的服务实例。

    文档参考:https://angular.io/guide/providers#providedin-and-ngmodules

    【讨论】:

    • If you want to limit the scope so no other developer will ever use your service outside of a particular module, use the provider's array of NgModule instead. @mick 我认为这个说法是错误的,如果你将服务添加到一个 Eager 模块的 providers 数组中,那么它也可以从其他模块访问。
    • @RohithKP 查看我添加的更新,澄清这不是关于急切模块,而是懒惰模块。
    【解决方案2】:

    这个帖子已经很老了,但我会回答我在搜索这个主题时学到的知识,以供未来在这个帖子上绊倒的人。

    使用延迟加载将服务私有化的概念是正确的,原因如下:

    • 当一个模块被延迟加载时,它会创建自己的 Injector 上下文, 这是根注入器的子级(准确地说是父级注入器)。它们中的服务不会被推送到根注入器,因为在配置根注入器时它们没有被实例化。
    • Angular Doc 说,确定服务范围的方法之一是将它们提供给它自己的模块(假设 Module-A)。并且只有当任何其他模块 B 导入模块 A 时,它才会拥有该服务的提供者(来自模块 A),因此可以访问它。这实际上适用于惰性模块而不是急切模块,原因如下:

    • 当您为 Eager 模块实现上述范围方法时,它将为该模块的服务创建一个提供程序(假设模块 A)。但是,当特定模块“A”被导入根模块时(就像所有急切的模块一样),根注入器将创建该服务的单个实例,并丢弃根注入器范围内该服务的任何重复实例(如果模块 A 被导入到任何其他急切模块中)。因此,所有 Eager 模块都可以访问根模块中导入的任何模块的单例服务。

    • 再次在应用程序加载时,根注入器和模块不会知道惰性模块及其服务。因此,惰性服务在它们自己的模块中被私有化。现在根模块可以访问惰性服务,它需要遵循导入模块的角度方式。这基本上是在应用程序加载时将“应该被延迟加载”的模块导入根模块,从而破坏了延迟加载的目的。
    • 如果您仍想从根注入器访问惰性服务。您可以使用:

      @Injectable({ 
          providedIn: 'root'
      })
      

    惰性服务中的装饰器并将其注入根注入器中,而不在应用程序加载时加载惰性模块。

    如果您在没有providedIn: root 对象的情况下可以访问根模块中的延迟服务,那么您所遵循的示例并不是延迟加载的真正实现。你可以通过这个链接:https://angular.io/guide/providers#limiting-provider-scope-by-lazy-loading-modules

    【讨论】:

    • this provideIn 仅适用于 Angular 6。以下版本的任何选项?不幸的是,我将它与 Ionic 一起使用,升级并不那么容易
    • @DanielRodriguez 你真正想要的解决方案是什么?
    • 我的问题是当您有 3 个页面并且您需要在其中 2 个页面上使用相同的提供程序和相同的实例时。最好的方法是什么? stackoverflow.com/questions/52100287/…
    • @DanielRodriguez 让我们继续讨论您的问题主题
    • 在父模块中声明为可注入的服务应该在子模块中可用,对吗?在我的例子中,这个服务被创建了两次:stackoverflow.com/questions/57688978/… 知道什么可以解决这个问题吗?
    【解决方案3】:

    这是我的做法:https://stackblitz.com/edit/angular-lazy-service-module?file=src%2Fapp%2Fapp.component.ts

    这是一个概念证明。您需要注意您使用的注入器(以防延迟服务需要一些依赖项)以及如何管理延迟加载服务的生命周期(创建多少实例等)。

    我的用例是在应用程序的多个区域中使用了一个相当大的服务(导出到 excel,超过 400 KB gzip),但我不想在实际需要之前加载/解析它 - 更快初始负载! (实际上我还使用了延迟预加载策略,在几秒钟后加载模块)。

    基本思想是您将其定义为路由中的惰性模块(您实际上并未使用),但您手动触发加载。您还可以使用注入令牌从该模块(一旦拥有它)解析服务。

    懒惰的模块

    import { NgModule } from '@angular/core';
    
    import { LazyService } from './lazy-service.service';
    import { LAZY_SERVICE_TOKEN } from './lazy-service.contract';
    
    @NgModule({
      providers: [{ provide: LAZY_SERVICE_TOKEN, useClass: LazyService }],
    })
    export class LazyServiceModule {
    }
    

    懒惰的服务

    import { Injectable } from '@angular/core';
    import { LazyService as LazyServiceInterface } from './lazy-service.contract';
    
    @Injectable()
    export class LazyService implements LazyServiceInterface {
      process(msg: string) {
        return `This message is from the lazy service: ${msg}`;
      }
    }
    

    应用模块

    @NgModule({
      imports: [BrowserModule,
        RouterModule.forRoot([
          // whatever other routes you have
          {
            path: '?$lazy-service', //some name that will not be used
            loadChildren: 'app/lazy-service/lazy-service.module#LazyServiceModule',
          },
        ])],
      declarations: [AppComponent],
      bootstrap: [AppComponent]
    })
    export class AppModule { }
    

    在组件中使用它

    constructor(
      private loader: NgModuleFactoryLoader,
      private injector: Injector,
    ) {
    }
    
    async loadServiceAndCall() {
      const factory = await this.loader.load('app/lazy-service/lazy-service.module#LazyServiceModule');
      const moduleRef = factory.create(this.injector);
      const service: LazyService = moduleRef.injector.get(LAZY_SERVICE_TOKEN);
      this.value = service.process('"from app.component.ts"')
    }
    

    【讨论】:

      【解决方案4】:

      我能给你的最好解释是this article

      无论如何,简而言之:

      • 在编译阶段会合并所有模块。
      • 当急切加载 Angular 编译器时,将所有服务放入 rootInjector 中,从而使整个应用程序都可以使用该服务。
        • 如果多个模块提供具有相同令牌的服务,则导入其他模块的模块中定义的提供者总是获胜。
        • 最后导入的模块中的提供程序会覆盖前面模块中的提供程序,但导入它们的模块除外。
      • 当lazyLoaded 时,每个模块在编译时仍会合并为一个,但会为每个模块创建一个注入器。由此,存在注入器层次结构,并且组件查找注入令牌的方式正在攀登层次结构,为该令牌寻找更接近的提供者。
      • forRoot()* 仅当您的模块具有您希望为整个应用程序提供的服务以及仅为某些模块的子模块提供其他服务时使用的约定。

      【讨论】:

        【解决方案5】:
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-02-22
        • 2021-07-13
        • 2018-04-10
        • 2017-06-21
        • 1970-01-01
        • 2021-11-29
        相关资源
        最近更新 更多