繁体   English   中英

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

[英]What is the purpose of providedIn with the Injectable decorator when generating Services in Angular 6?

在 Angular CLI 中生成服务时,它会添加额外的元数据,该元数据具有“提供的”属性,默认为“根”,用于可注入装饰器。

@Injectable({
  providedIn: 'root',
})

providedIn 到底是做什么的? 我假设这使该服务像整个应用程序的“全局”类型单例服务一样可用,但是,在 AppModule 的提供程序数组中声明此类服务不是更干净吗?

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

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

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

顺便提一句:

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

来自文档

什么是可注入装饰器?

将类标记为可供 Injector 创建。

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

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

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

providedIn 到底是做什么的?

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

providedIn: Type<any> | 'root' | null

提供在:“根”

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

提供在:模块

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

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

@Injectable({
  providedIn: UserModule,
})
export class UserService {
}

这种方法是首选的,因为如果没有注入服务,它会启用服务的 Tree-shaking( Tree -shaking是构建过程中的一个步骤,从代码库中删除未使用的代码)。

如果无法在服务中指定应该由哪个模块提供它,您还可以在模块中为服务声明一个提供者:

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

@NgModule({
  providers: [UserService],
})
export class UserModule {
}

如果你使用providedIn,则注入被注册为模块的提供者,而不将它添加到模块的提供者中。

来自Docs

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

请参阅@Nipuna的出色解释,

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

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

@Injectable()

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

像这样;

数据.service.ts ↴

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

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

    // Code . . .
}

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 {}

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

数据.service.ts ↴

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

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

    // Code . . .
}

那么我们的模块应该是这样的:

app.module.ts ↴

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

@NgModule({
    declarations: [AppComponent],
    providers: [],
    imports: [...],
    bootstrap: [AppComponent],
})
export class AppModule {}

看到这次我没有在providers数组中添加DataService ,因为它不是必需的。

良好实践

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

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

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

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

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

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

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

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

让我添加一个新的回复,它与 Angular 9(后 Ivy)是最新的,并且在 2022 年应该是正确的。


providedIn到底是做什么的?

这是一个决定哪些注射器将为您提供 Injectable 的设置。 (Injectable = 任何用@Injectable装饰的类,例如服务)。

当一个 Injectable 被“提供”时,这基本上意味着:“一个 Injectable 的实例将被提供给低于这个特定注入器级别的类,只要他们请求它”。

当它被“注入”时,这意味着:“一个类构造函数已请求提供一些服务实例,因此 Angular 将尝试为它提供可以在注入器层次结构中找到的最近实例”。

“注入器层次结构”本质上是“注入器范围树”,通常按platform -> root -> module -> component顺序组织。


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

providedIn: Type<any> | 'root' | 'platform' | 'any' | null

providedIn: 'platform'

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

providedIn: 'root'

Angular 将创建MyService的单个共享实例,并使其可用于应用程序中的所有类。

providedIn: 'any'

Angular 将创建MyService的单个共享实例,并使其可用于热加载模块中的所有类。

但是,每个延迟加载的模块都会创建自己的、新的、单独的MyService实例(然后只能在该模块内的类中使用)。

providedIn: MyModule

如果加载了MyModule ,Angular 只会创建MyService的实例。

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

但是,如果MyModule延迟加载的,则此实例只能由MyModule内部的类使用。

providedIn: MyComponent

每当MyComponent被实例化时,Angular 都会创建一个新的MyService实例。

MyService实例只能由该特定MyComponent实例的后代使用,并且将在组件被销毁时立即被销毁。

providedIn: null

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

每当实例化该模块/组件时,它将获得一个新的MyService实例,该实例仅可在其特定范围内使用。 (下面有完整的描述。)


提供的最后一种方法,无论providedIn的设置如何,都可以使用,是将您的 Injectable 添加到模块或组件中的providers数组中:

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

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

这将创建一个单独的MyService实例以在该特定模块或组件中使用。 它的作用域类似于上面的providedIn: MyModuleprovidedIn: MyComponent示例。

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

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


补充说明:

providedIn如何影响 tree-shaking?

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

如果在其分配的注入器范围内没有任何(急切或延迟加载的)类注入,则使用providedIn配置的 Injectable 将被摇树。

但是,分配给某些模块/组件中的providers数组的 Injectable永远不会被摇树,即使它没有在任何地方注入。

在 AppModule 的 provider 数组中声明这样的服务不是更干净吗?

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

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

AppComponentAppModule中提供有区别吗?

是的。 设置提供者: AppModule中的providers: [MyService]将使MyService可用于延迟加载的模块。 不会将其添加到AppComponent中的providers数组中。

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

根据Documentation

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

简单地..

providedIn :'root'为整个应用程序创建一个实例,而无需从任何NgModule提供它。 只需通过@Injectable装饰器在服务中声明它。

如果您想为任何组件拥有此服务的一个新实例,请通过组件的提供者声明它。 这将为此组件及其子组件(如果有)创建另一个新实例。 因此,您可以拥有一个具有全局范围的实例和一个组件的另一个实例。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM