当我们在创建 angular 的服务时,providedIn的选项默认都是root,有的小伙伴就会问了,这个providedIn是做什么的,除了 root 还能填其他值嘛 🤔。
查阅angular(v15)文档,发现providedIn总共有 5 种值
root:在大多数应用程序中是指应用程序级注入器。any:在每个惰性加载的模块中提供一个唯一实例,而所有热切加载的模块共享一个实例。null:等效于undefined。可注入物不会在任何范围内自动提供,必须添加到@NgModule、@Component或@Directive的providers数组中。platform:由页面上所有应用程序共享的特殊单例平台注入器。Type<any>- 将可注入物与@NgModule或其他InjectorType相关联。此选项已弃用。
angular 官网的文档一如既往的不给力 😳,只有选项,并没有示例说明,下面我们通过几个具体的例子,来看看这几个选项具体的表现 😎
root
providedIn 最常见的就是 root,因为服务创建时,默认值都是 root,
root 就是在项目根目录创建一个实例,所有注入这个服务的组件,都会共享这个实例。 利用这个特性,我们经常使用它去做组件间的传值,或者做全局的传值。
我们按照下面步骤,创建一个示例
- 创建 2 个
module, 分别命名为root-first、root-second,并且使用路由懒加载 - 创建
rootService,providedIn为root, 创建变量count - 给 2 个
moudlue中的component都引入rootService, 显示变量count,并且添加+按钮使count + 1
ts
import { Component, NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { RouterModule } from "@angular/router";
import { Injectable } from "@angular/core";
@Injectable({
providedIn: "root",
})
export class RootService {
count = 0;
constructor() {}
}
// 第一个module
@Component({
selector: "root-first",
template: `
<div>
root-first: {{ service.count }}
<button (click)="service.count = service.count + 1">+</button>
<a [routerLink]="'/root-second'"> second </a>
</div>
`,
styles: [""],
})
export class RootFirstComponent {
constructor(public service: RootService) {}
}
@NgModule({
imports: [
RouterModule,
RouterModule.forChild([
{
path: "",
component: RootFirstComponent,
},
]),
],
declarations: [RootFirstComponent],
})
export class RootFirstModule {}
// 第二个module
@Component({
selector: "root-second",
template: `
<div>
root-second: {{ service.count }}
<button (click)="service.count = service.count + 1">+</button>
<a [routerLink]="'/root-first'"> first </a>
</div>
`,
styles: [""],
})
export class RootSecondComponent {
constructor(public service: RootService) {}
}
@NgModule({
imports: [
RouterModule,
RouterModule.forChild([
{
path: "",
component: RootSecondComponent,
},
]),
],
declarations: [RootSecondComponent],
})
export class RootSecondModule {}
// app component, app module
@Component({
selector: "app-root",
template: ` <router-outlet></router-outlet> `,
styles: [``],
})
export class AppComponent {}
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
RouterModule,
RouterModule.forRoot([
{
path: "root-first",
loadChildren: () =>
import("./app.module").then((val) => val.RootFirstModule),
},
{
path: "root-second",
loadChildren: () =>
import("./app.module").then((val) => val.RootSecondModule),
},
]),
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}

如图,我们可以看到,在 root-first 中改变变量的值,在 root-second 中一样会起效,同样在 root-second 中改值,root-first 也会生效。
any
每个懒加载的模块中会分别共享一个实例,而所有非懒加载的模块共享一个实例
什么意思呢,同样是上面的代码,我们把 service 中的providedIn改为any,这个时候,在 root-first 中改变值,root-second 中的值就不会变化了
ts
@Injectable({
providedIn: "any",
})
export class RootService {
count = 0;
constructor() {}
}

这个时候如果我们把 root-first 改为急性加载,并在 app.component 中也注入 root.service
ts
import { Component, NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { RouterModule } from "@angular/router";
import { Injectable } from "@angular/core";
@Injectable({
providedIn: "any",
})
export class RootService {
count = 0;
constructor() {}
}
// 第一个module
@Component({
selector: "root-first",
template: `
<div>
root-first: {{ service.count }}
<button (click)="service.count = service.count + 1">+</button>
<a [routerLink]="'/root-second'"> second </a>
</div>
`,
styles: [""],
})
export class RootFirstComponent {
constructor(public service: RootService) {}
}
@NgModule({
imports: [
RouterModule,
RouterModule.forChild([
{
path: "root-first",
component: RootFirstComponent,
},
]),
],
declarations: [RootFirstComponent],
})
export class RootFirstModule {}
// 第二个module
@Component({
selector: "root-second",
template: `
<div>
root-second: {{ service.count }}
<button (click)="service.count = service.count + 1">+</button>
<a [routerLink]="'/root-first'"> first </a>
</div>
`,
styles: [""],
})
export class RootSecondComponent {
constructor(public service: RootService) {}
}
@NgModule({
imports: [
RouterModule,
RouterModule.forChild([
{
path: "",
component: RootSecondComponent,
},
]),
],
declarations: [RootSecondComponent],
})
export class RootSecondModule {}
// app component, app module
@Component({
selector: "app-root",
template: `
app header: {{ service.count }}
<button (click)="service.count = service.count + 1">+</button>
<router-outlet></router-outlet>
`,
styles: [``],
})
export class AppComponent {
constructor(public service: RootService) {}
}
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
RouterModule,
RootFirstModule,
RouterModule.forRoot([
// {
// path: 'root-first',
// loadChildren: () =>
// import('./app.module').then((val) => val.RootFirstModule),
// },
{
path: "root-second",
loadChildren: () =>
import("./app.module").then((val) => val.RootSecondModule),
},
]),
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
这个时候我们发现,在 app.component 和 root-first 中 count 是共享的 🤓,但是 root-second 中的 count 是不与他们共享的, 这是因为 root-second 是懒加载的,而其他的则是急性加载所以他们会共享数据

null
如果我们把providedIn设为 null 或者不填,那么此时,就需要在 module 中的 providers 注入这个服务,否则就会报错 😲
ts
@Injectable({
providedIn: null,
})
export class RootService {
count = 0;
constructor() {}
}

此时我们在所有用到 root.service 服务的 module 中全部注入 service, 它的表现形式和 any 一样,即在每个懒加载的模块中会分别共享一个实例,而所有非懒加载的模块共享一个实例
ts
import { Component, NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { RouterModule } from "@angular/router";
import { Injectable } from "@angular/core";
@Injectable({
providedIn: null,
})
export class RootService {
count = 0;
constructor() {}
}
// 第一个module
@Component({
selector: "root-first",
template: `
<div>
root-first: {{ service.count }}
<button (click)="service.count = service.count + 1">+</button>
<a [routerLink]="'/root-second'"> second </a>
</div>
`,
styles: [""],
})
export class RootFirstComponent {
constructor(public service: RootService) {}
}
@NgModule({
imports: [
RouterModule,
RouterModule.forChild([
{
path: "root-first",
component: RootFirstComponent,
},
]),
],
declarations: [RootFirstComponent],
// 注入服务
providers: [RootService],
})
export class RootFirstModule {}
// 第一个module
@Component({
selector: "",
template: `
<div>
root-second: {{ service.count }}
<button (click)="service.count = service.count + 1">+</button>
<a [routerLink]="'/root-first'"> first </a>
</div>
`,
styles: [""],
})
export class RootSecondComponent {
constructor(public service: RootService) {}
}
@NgModule({
imports: [
RouterModule,
RouterModule.forChild([
{
path: "",
component: RootSecondComponent,
},
]),
],
declarations: [RootSecondComponent],
// 注入服务
providers: [RootService],
})
export class RootSecondModule {}
// app component, app module
@Component({
selector: "app-root",
template: `
app header: {{ service.count }}
<button (click)="service.count = service.count + 1">+</button>
<router-outlet></router-outlet>
`,
styles: [``],
})
export class AppComponent {
constructor(public service: RootService) {}
}
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
RouterModule,
RootFirstModule,
// RootSecondModule,
RouterModule.forRoot([
{
path: "root-first",
loadChildren: () =>
import("./app.module").then((val) => val.RootFirstModule),
},
{
path: "root-second",
loadChildren: () =>
import("./app.module").then((val) => val.RootSecondModule),
},
]),
],
// 注入服务
providers: [RootService],
bootstrap: [AppComponent],
})
export class AppModule {}
platform
页面上所有应用程序共享的平台注入器的特殊单例。
创建 appMoudle2
ts
// 第二个根模块
@Component({
selector: "app-root2",
template: `
app-root2 header: {{ service.count }}
<button (click)="service.count = service.count + 1">+</button>
`,
styles: [``],
})
export class AppComponent2 {
constructor(public service: RootService) {}
}
@NgModule({
declarations: [AppComponent2],
imports: [BrowserModule, RouterModule],
bootstrap: [AppComponent2],
})
export class AppModule2 {}
同时在main.ts中引入
ts
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { AppModule, AppModule2 } from "./app/app.module";
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));
platformBrowserDynamic()
.bootstrapModule(AppModule2)
.catch((err) => console.error(err));
在index.html中引入app-root2
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>AngularService</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<app-root></app-root>
<hr />
<app-root2></app-root2>
</body>
</html>
然后我们把 root.service 的providedIn设为platform
ts
@Injectable({
providedIn: "platform",
})
export class RootService {
count = 0;
constructor() {}
}
这时,我们发现虽然是 2 个根模块,但是数据还是共享了(没有实时刷新的原因,是因为分属不同根模块,没有触发脏检查)

ngModule
在这里我们新创建一个 ngModule,命名为RootChildModule, 服务的providedIn我们设置RootChildModule,并且在app.module中引入RootChildModule
ts
@NgModule()
export class RootChildModule {}
@Injectable({
providedIn: RootChildModule,
})
export class RootService {
count = 0;
constructor() {
console.log("--lucky---");
}
}
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
RouterModule,
RootChildModule,
RouterModule.forRoot([
{
path: "root-first",
loadChildren: () =>
import("./app.module").then((val) => val.RootFirstModule),
},
{
path: "root-second",
loadChildren: () =>
import("./app.module").then((val) => val.RootSecondModule),
},
]),
],
bootstrap: [AppComponent],
})
export class AppModule {}
此时的效果与root相同,不同之处在于,只有在 component 中引入 root.service,该服务的代码才会被打包进去,否则代码将会被摇树优化。
为了验证此功能,我们把 root.service 移出,新建/child/root.service 文件
ts
import { Injectable } from "@angular/core";
import { RootChildModule } from "./root-child.module";
@Injectable({
providedIn: RootChildModule,
})
export class RootService {
count = 0;
constructor() {
console.log("--lucky---");
}
}
同时新建/child/root-child.module 来给 providedIn 提供值
ts
import { NgModule } from "@angular/core";
@NgModule()
export class RootChildModule {}
然后修改一下app.module的代码
ts
import { Component, NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { RouterModule } from "@angular/router";
import { RootChildModule } from "./child/root-child.module";
// 第一个模块
@Component({
selector: "root-first",
template: ` <div>root-first</div> `,
styles: [""],
})
export class RootFirstComponent {
constructor() {}
}
@NgModule({
imports: [
RouterModule,
RootChildModule,
RouterModule.forChild([
{
path: "",
component: RootFirstComponent,
},
]),
],
declarations: [RootFirstComponent],
})
export class RootFirstModule {}
// app component, app module
@Component({
selector: "app-root",
template: `
app header
<router-outlet></router-outlet>
`,
styles: [``],
})
export class AppComponent {
constructor() {}
}
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
RouterModule,
RouterModule.forRoot([
{
path: "root-first",
loadChildren: () =>
import("./app.module").then((val) => val.RootFirstModule),
},
]),
],
bootstrap: [AppComponent],
})
export class AppModule {}
注意,此时,我们没有在任何地方注入root.service, 此时我们在浏览器 network 中搜索 root.service 中的代码

结果可知,代码没有打包进去
此时,我们在RootFirstComponent中注入root.service
ts
import { RootService } from "./child/root.service";
// 第一个模块
@Component({
selector: "root-first",
template: ` <div>root-first</div> `,
styles: [""],
})
export class RootFirstComponent {
constructor(public rootService: RootService) {}
}
此时,再去 network 中搜索,发现已有代码

我们再删除RootFirstComponent中已注入的root.service,并且把providedIn的值改为null,并且在RootFirstModule里面申明RootService
ts
import { Injectable } from "@angular/core";
import { RootChildModule } from "./root-child.module";
@Injectable({
providedIn: null,
})
export class RootService {
count = 0;
constructor() {
console.log("--lucky---");
}
}
ts
import { RootService } from "./child/root.service";
// 第一个模块
@Component({
selector: "root-first",
template: ` <div>root-first</div> `,
styles: [""],
})
export class RootFirstComponent {
constructor() {}
}
@NgModule({
imports: [
RouterModule,
RootChildModule,
RouterModule.forChild([
{
path: "",
component: RootFirstComponent,
},
]),
],
providers: [RootService],
declarations: [RootFirstComponent],
})
export class RootFirstModule {}
此时搜索 network, 虽然已经删除了注入部分的代码,但是 root.service 中的代码依然能搜索到

这里,我们不用providedIn:root举例的原因是,服务的 providedIn 为 root 时,不用声明,也能使用,所以自然代码也会被摇树优化掉,我想这也是 ngModule这个选项被弃用的原因。
总结
实际中,我们使用比较多的只有root和null,
root同时具备摇树优化的能力,但是他会在全局共享数据,并且没有限制,任意地方都可以使用null可以根据情况,在需要的时候使用providers注入使用,并且会区分懒加载和急性加载
platform虽然有实际的应用场景,但是使用比较少,而any和ngModule都可以被any和root取代