在Angular 6中生成服务时,使用Injectable装饰器提供的目的是什么?

Ste*_*nar 77 angular angular6

在Angular CLI中生成服务时,它会添加带有'provided in'属性的额外元数据,其默认值为'root',用于Injectable装饰器.

@Injectable({
  providedIn: 'root',
})
Run Code Online (Sandbox Code Playgroud)

究竟做了什么?我假设这使得服务可用作整个应用程序的"全局"类型单例服务,但是,在AppModule的提供者数组中声明此类服务会不会更清晰?

更新:

对于其他任何人,以下段落也提供了另一个很好的解释,特别是如果您想要仅为功能模块提供服务.

"现在有一种新的,推荐的方法,使用新的provideIn属性直接在@Injectable()装饰器中注册提供者.它接受'root'作为值或应用程序的任何模块.当你使用'root ',您的注入将在应用程序中注册为单例,并且您不需要将其添加到根模块的提供者.同样,如果您使用providedIn:UsersModule,注入可注册为UsersModule的提供者无需将其添加到模块的提供者." - https://blog.ninja-squad.com/2018/05/04/what-is-new-angular-6/

更新2:

经过进一步调查,我认为它只是有用 @Injectable()

如果要在除根模块之外的任何模块中提供服务,那么最好在功能模块的装饰器中使用providers数组,否则您将受到循环依赖性的困扰.这里有趣的讨论 - https://github.com/angular/angular-cli/issues/10170

Sen*_*the 61

这是一个最新的回复,与 Angular 9+(后 Ivy)保持同步,并且应该在 2022 年正确。


TL;DR:这一切都是为了控制将创建多少个服务实例,以及创建后它们在哪里可用。


术语:

  • 可注入- 任何用 装饰的类@Injectable,例如服务。

  • 注入器- 一个 Angular 类,能够为其下面的类提供注入。(这包括所有组件和模块。)

  • 注入器范围/级别- 位于特定注入器“下方”的所有类实例的范围。

  • 注入器层次结构- 按顺序组织的注入器范围的优先化树platform -> root -> module -> component

  • 提供了Injectable - Injectable 的实例将在低于此特定注入器级别的类请求时提供给它们。

  • Injectable 被注入- 类构造函数已请求提供服务的某个实例,因此 Angular 将尝试为其提供可以在注入器层次结构中找到的最近的实例”。

  • tree-shaking - 由于 Angular 编译器而自动发生的优化。当它检测到某些代码未被使用时,该代码将从应用程序的最终编译(或给定延迟加载模块的编译)中删除。

您应该已经知道的其他术语:类、实例、模块、组件、惰性/急切加载模块


问:具体是providedIn做什么的?

这是一个设置,决定哪些注射器应该提供您的注射物。

假设我们创建一个名为 的 Injectable MyService,并了解所有选项的作用。

providedIn: Type<any> | 'root' | 'platform' | 'any' | null
Run Code Online (Sandbox Code Playgroud)

providedIn: 'platform'

Angular 将创建并向页面上的MyService所有 Angular应用程序提供单个共享实例。(如果您使用微前端架构,这仅与高级用例相关。)

providedIn: 'root'

Angular 将创建一个共享实例MyService并将其提供给应用程序中的所有类。

providedIn: 'any'// 自 Angular v15 起已弃用

Angular 将创建 的单个共享实例MyService并将其提供给急切加载的模块中的所有类。

但是,每个延迟加载的模块将提供其自己的、新的、单独的实例MyService(该实例仅在该模块内的类中可用)。

providedIn: MyModule// 自 Angular v15 起已弃用

MyService如果MyModule加载,Angular 只会创建一个实例。

如果MyModule热切加载的,那么从现在开始,该实例将可供所有其他热切加载的模块使用。(请注意,这实际上与 相同providedIn: 'root'。)

但是,如果MyModule延迟加载MyModule,则每当加载该实例时,只会为内部的类提供该实例。

providedIn: MyComponent// 自 Angular v15 起已弃用

MyService无论何时实例化,Angular 都会创建一个新的、新鲜的实例MyComponent

MyService实例只会提供给该特定MyComponent 实例的后代,并且一旦组件实例被销毁,该实例就会被销毁。(请注意,这意味着MyService每次渲染此组件时都会创建一个新组件。)

providedIn: null

MyService只能通过添加到providers特定模块或组件中的数组来实例化。

每当该模块/组件被实例化时,它将创建 的一个新实例MyService,并仅在其特定范围内提供它。(请参阅providers下面的数组的完整说明。)


问:providers数组有什么作用?

任何注入器都可以用数组来设置providers

@NgModule({
  providers: [MyService],
})

@Component({
  providers: [MyService],
})
Run Code Online (Sandbox Code Playgroud)

所有 Injectables 都可以添加到providers数组中,无论providedIn设置如何。

添加MyServiceproviders数组将导致注入器创建并向其范围内的类提供一个完全独立的实例。providedIn: MyModule(范围与上面示例中描述的完全相同providedIn: MyComponent。)

这种提供方法不支持tree-shaking。该服务将始终包含在编译中,即使没有人使用它。(请参阅下面的摇树笔记。)

问:为什么我要同时使用providersarray和? providedIn

一个示例用例可能是如果MyService并且providedIn: 'root'已经有一个共享实例,但您希望您的模块/组件有自己的单独实例。


补充笔记:

providedIn问: /设置如何providers影响 tree-shaking?

如果配置的providedInInjectable未由其分配的注入器范围内的任何(热切或延迟加载)类注入,则该 Injectable 将进行树摇动。

然而,分配给某些模块/组件中的providers数组的 Injectable永远不会被 tree-shaking,即使它没有被注入到任何地方。

为了使 tree-shaking 最有效,您应该始终使用providedIn数组providers

问:providedIn: 'root'如果我认为使用providers数组AppModule看起来更干净,为什么要使用?

如上所述,这两种方法的主要区别在于,providedIn支持tree-shaking,而providers数组不支持。

除此之外,这是一个架构决策:如果我们providedIn直接在 Injectable 文件中设置,则 Injectable拥有应如何提供它的决定。区分谁拥有合约对于必须在数百个模块之间进行合作的大型应用程序和团队具有重大影响。

问:在或中设置providers: [MyService]数组有区别吗?AppComponentAppModule

是的。当您在而非中执行时,MyService才会在延迟加载模块中提供。AppModuleAppComponent

(这是因为延迟加载的模块依赖于Router,它被导入到AppModule,比 更高的一个注入器范围AppComponent。)


Saj*_*ran 37

如果使用providedIn,则注入可注册为模块的提供者,而不会将其添加到模块的提供者.

Docs

服务本身是CLI生成的类,并使用@Injectable进行修饰.默认情况下,此装饰器配置了providedIn属性,该属性为服务创建提供程序.在这种情况下,providedIn:'root'指定应在根注入器中提供服务.

  • @ prolink007.使用providedIn允许应用程序延迟加载服务.要对此进行测试,请将控制台日志放入服务中.我的主页用于加载16个服务,现在它加载了9个.很难量化性能,但我知道我不会在需要之前加载服务:) (15认同)
  • 谢谢Sajeetharan.好的,所以听起来这是指定应该提供服务的新方法.我想我最初的偏好是查看模块的提供者列表,以查看声明为提供者的所有服务,而不是仔细阅读ProvideIn标记的分散代码库....(?) (3认同)
  • 保持 AppModule / CoreModule 定义小一点;) (3认同)
  • Angular 有什么理由添加这个吗?这是要解决的问题吗?我不认为这是有原因的。 (2认同)
  • 您可以通过使用`providedIn`属性来定义您的服务,使其在使用@Injectable()装饰器时可以在哪里初始化服务。然后应该将其从NgModule声明的provider属性以及其import语句中删除,这可以通过从捆绑中删除未使用的代码来减小捆绑的大小。 (2认同)

Mic*_*ick 23

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

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

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

BTW:

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


Nip*_*una 11

从文档

什么是可注射装饰器?

将一个类标记为可用于Injector进行创建。

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class UserService {
}
Run Code Online (Sandbox Code Playgroud)

服务本身是CLI生成的类,并用@Injectable()装饰。

ProvideIn到底做什么?

通过将其与@NgModule或其他InjectorType相关联,或通过指定应在“根”注入器中提供此注入物来确定哪些注入器将提供可注入物,“根”注入器将在大多数应用程序中成为应用程序级注入器。

providedIn: Type<any> | 'root' | null
Run Code Online (Sandbox Code Playgroud)

providerIn:'root'

当您在根级别提供服务时,Angular会创建一个共享的服务实例,并将其注入到任何需要该服务的类中。在@Injectable()元数据中注册提供程序还允许Angular通过从编译的应用程序中删除服务(如果未使用该服务)来优化应用程序。

includedIn:模块

也可以指定应在特定的@NgModule中提供服务。例如,如果您不希望应用程序使用服务,除非它们导入您创建的模块,则可以指定应在模块中提供服务

import { Injectable } from '@angular/core';
import { UserModule } from './user.module';

@Injectable({
  providedIn: UserModule,
})
export class UserService {
}
Run Code Online (Sandbox Code Playgroud)

此方法是首选方法,因为如果没有注入它,它将启用服务的树状摇动(树状摇动是构建过程中的一个步骤,该过程将从代码库中删除未使用的代码)。

如果无法在服务中指定应由哪个模块提供服务,则还可以在模块内声明服务的提供者:

import { NgModule } from '@angular/core';
import { UserService } from './user.service';

@NgModule({
  providers: [UserService],
})
export class UserModule {
}
Run Code Online (Sandbox Code Playgroud)

  • 当其 @Injectable() 没有附加的“providedIn”元时,则必须在模块级别的提供者数组中声明它。然后,它将不可用于其他模块,并成为该模块分支所独有的。 (5认同)
  • 这个答案比角度文档中的定义更好。非常清楚。 (4认同)
  • 最好的解释。 (2认同)
  • 解释得非常好,非常感谢! (2认同)

Coc*_*ine 11

请参阅@Nipuna 的出色解释,

我想通过添加示例来扩展它。

如果你只使用没有providedin属性的Injectable 装饰器,比如,

@Injectable()
Run Code Online (Sandbox Code Playgroud)

那么您必须在相应的模块providers数组中写入服务的名称。

像这样;

数据服务.ts ?

import { Injectable } from '@angular/core';

@Injectable()
export class DataService {
    constructor() {}

    // Code . . .
}
Run Code Online (Sandbox Code Playgroud)

app.module.ts ?

import { AppComponent } from './app.component';
import { DataService } from './core/data.service';

@NgModule({
    declarations: [AppComponent],
    providers: [DataService],    // ? LOOK HERE WE PROVIDED IT
    imports: [...],
    bootstrap: [AppComponent],
})
export class AppModule {}
Run Code Online (Sandbox Code Playgroud)

但是,如果你使用providedIn: 'root',像这样:

数据服务.ts ?

import { Injectable } from '@angular/core';

@Injectable({
    providedIn: 'root',
})
export class DataService {
    constructor() {}

    // Code . . .
}
Run Code Online (Sandbox Code Playgroud)

然后我们的模块看起来像这样:

app.module.ts ?

import { AppComponent } from './app.component';
import { DataService } from './core/data.service';

@NgModule({
    declarations: [AppComponent],
    providers: [],
    imports: [...],
    bootstrap: [AppComponent],
})
export class AppModule {}
Run Code Online (Sandbox Code Playgroud)

见我没加DataServiceproviders阵列这段时间,因为这是没有必要的。

良好做法

这可能会派上用场,来自Angular Guides

请在服务的 @Injectable 装饰器中使用应用程序根注入器提供服务。

为什么?Angular 注入器是分层的。

为什么?当您向根注入器提供服务时,该服务的实例在需要该服务的每个类中共享和可用。当服务共享方法或状态时,这是理想的。

为什么?当您在服务的 @Injectable 装饰器中注册服务时,Angular CLI 的生产构建使用的优化工具可以执行摇树并删除您的应用程序未使用的服务。

为什么?当两个不同的组件需要不同的服务实例时,这并不理想。在这种情况下,最好在需要新的单独实例的组件级别提供服务。


小智 6

includedIn告诉Angular根注入器负责创建您的Service实例。通过这种方式提供的服务会自动提供给整个应用程序使用,不需要在任何模块中列出。

服务类可以充当自己的提供者,这就是为什么在@Injectable装饰器中定义它们是您需要的所有注册的原因。


Maa*_*rti 5

根据Documentation

在 @Injectable() 元数据中注册提供程序还允许 Angular 通过从编译的应用程序中删除不使用的服务来优化应用程序。