一文彻底搞懂providedIn的所有选项,除了root还能选择啥

当我们在创建 angular 的服务时,providedIn的选项默认都是root,有的小伙伴就会问了,这个providedIn是做什么的,除了 root 还能填其他值嘛 🤔。

查阅angular(v15)文档,发现providedIn总共有 5 种值

  • root:在大多数应用程序中是指应用程序级注入器。
  • any:在每个惰性加载的模块中提供一个唯一实例,而所有热切加载的模块共享一个实例。
  • null:等效于 undefined 。可注入物不会在任何范围内自动提供,必须添加到@NgModule@Component@Directiveproviders 数组中。
  • platform:由页面上所有应用程序共享的特殊单例平台注入器。
  • Type<any> - 将可注入物与 @NgModule 或其他 InjectorType 相关联。此选项已弃用。

angular 官网的文档一如既往的不给力 😳,只有选项,并没有示例说明,下面我们通过几个具体的例子,来看看这几个选项具体的表现 😎

root

providedIn 最常见的就是 root,因为服务创建时,默认值都是 root

root 就是在项目根目录创建一个实例,所有注入这个服务的组件,都会共享这个实例。 利用这个特性,我们经常使用它去做组件间的传值,或者做全局的传值。

我们按照下面步骤,创建一个示例

  1. 创建 2 个 module, 分别命名为 root-firstroot-second,并且使用路由懒加载
  2. 创建rootService, providedInroot, 创建变量count
  3. 给 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.componentroot-firstcount 是共享的 🤓,但是 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.serviceprovidedIn设为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这个选项被弃用的原因。

总结

实际中,我们使用比较多的只有rootnull

  • root同时具备摇树优化的能力,但是他会在全局共享数据,并且没有限制,任意地方都可以使用
  • null可以根据情况,在需要的时候使用providers注入使用,并且会区分懒加载和急性加载

platform虽然有实际的应用场景,但是使用比较少,而anyngModule都可以被anyroot取代

源码

angular-injectable-providedIn

相关推荐
codingandsleeping1 分钟前
Express入门
javascript·后端·node.js
Vaclee4 分钟前
JavaScript-基础语法
开发语言·javascript·ecmascript
拉不动的猪26 分钟前
前端常见数组分析
前端·javascript·面试
小吕学编程42 分钟前
ES练习册
java·前端·elasticsearch
Asthenia04121 小时前
Netty编解码器详解与实战
前端
袁煦丞1 小时前
每天省2小时!这个网盘神器让我告别云存储混乱(附内网穿透神操作)
前端·程序员·远程工作
一个专注写代码的程序媛2 小时前
vue组件间通信
前端·javascript·vue.js
一笑code2 小时前
美团社招一面
前端·javascript·vue.js
懒懒是个程序员3 小时前
layui时间范围
前端·javascript·layui
NoneCoder3 小时前
HTML响应式网页设计与跨平台适配
前端·html