一文彻底搞懂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

相关推荐
Мартин.3 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
昨天;明天。今天。4 小时前
案例-表白墙简单实现
前端·javascript·css
数云界4 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd4 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常4 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
ChinaDragonDreamer4 小时前
Vite:为什么选 Vite
前端
小御姐@stella4 小时前
Vue 之组件插槽Slot用法(组件间通信一种方式)
前端·javascript·vue.js
GISer_Jing4 小时前
【React】增量传输与渲染
前端·javascript·面试
GISer_Jing4 小时前
WebGL在低配置电脑的应用
javascript
eHackyd4 小时前
前端知识汇总(持续更新)
前端