26

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

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

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

在情景 1 中,

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

在情景 2 中,

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

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

他们提到了以下内容。

一开始,

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

最后,

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

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

我很困惑。有人请澄清。

4

5 回答 5

27

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

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

有关更多信息,请考虑阅读文档NgModule 常见问题解答

顺便提一句:

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

*更新

'使用提供者的 NgModule 数组' 表示使用延迟加载模块的提供者数组,例如:

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

于 2018-10-26T12:33:42.067 回答
23

这个线程很老了,但我会回答我在搜索这个主题时学到的东西,以寻找这个线程上未来的绊脚石。

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

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

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

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

    @Injectable({ 
        providedIn: 'root'
    })
    

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

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

于 2018-08-25T09:40:28.410 回答
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"')
}
于 2019-02-13T16:00:37.887 回答
2

我能给你的最好的解释就是这篇文章

无论如何,简而言之:

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

对于延迟加载服务,您可以查看以下链接

[如何在延迟加载模块中提供服务并将该服务的范围限定为延迟加载模块及其组件?

于 2018-07-09T14:17:54.190 回答