qrcode

马上订阅,开启修仙之旅

Angular Provider 作用域

Services 是每个 Angular 应用程序的基本块之一。Service 是一个普通的 TypeScript 类,它也可以没有使用 @Injectable 装饰器。 比如下面是我们定义一个 UserService 类:

1
export class UserService {}

定义完 UserService 类之后,我们可以在 NgModule 中注册它:

1
2
3
4
5
6
7
8
import { UserService } from './user.service';
...
@NgModule({
imports: [ BrowserModule],
declarations: [ AppComponent],
bootstrap: [ AppComponent],
providers: [UserService]
})

在 Angular 6 之后,我们也可以利用 @Injectable 的元数据来配置服务类,如:

1
2
3
4
5
6
import { Injectable } from '@angular/core';

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

示例中 providedIn 的属性值 root 表示服务的作用域范围是根级作用域(AppModule)。

当你注册根级别的服务时,Angular 会创建一个单独的共享服务实例。如果在 @Injectable 元数据中注册服务,Angular 会在构建阶段自动剔除无用的服务,进而优化我们的应用程序。

因此当我们在跟模块中配置某个服务后,这个服务将在整个应用程序中可用。需要注意的是在非懒加载的特性模块中,如果我们也注册了同一个服务。在根模块和特性模块中是使用同一个服务实例,即服务是单例的。

“Talk is cheap,show me your code”。

非懒加载模块

下面我们先来定义一个 UserModule 模块,然后分别定义 UserService 服务和 UserComponent 组件:

user.module.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { UserService } from './user.service';
import { UserComponent } from './user.component';

@NgModule({
imports: [
CommonModule
],
declarations: [UserComponent],
providers: [UserService],
exports: [UserComponent]
})
export class UserModule { }

user.service.ts

1
2
3
4
5
6
7
8
9
10
11
12
import { Injectable } from '@angular/core';

@Injectable({
providedIn: "root"
})
export class UserService {
name = "semlinker";

changeName() {
this.name = 'Lolo';
}
}

user.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';

@Component({
selector: 'app-user',
template: `
<div class="border">
<p>
我是 {{userService.name}} ——(UserModule)
</p>
<button (click)="userService.changeName()">改名</button>
</div>
`,
styles: [`.border {border: 1px dashed red; padding: 5px;}`]
})
export class UserComponent {
constructor(private userService: UserService) { }
}

接下来我们再来定义 AppModule 与 AppComponent:

app.module.ts

1
2
3
4
5
6
7
8
9
10
11
12
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { UserModule } from './user/user.module';

import { AppComponent } from './app.component';

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

app.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Component } from '@angular/core';
import { UserService } from './user/user.service';

@Component({
selector: 'my-app',
template: `
<p> 我是 {{userName}} ——(AppModule)</p>
<app-user></app-user>
`
,
})
export class AppComponent {
constructor(private userService: UserService) { }

get userName() {
return this.userService.name;
}
}

在以上示例中,我们使用 providedIn: "root" 设置 UserService 的作用域范围,此外在 UserModule 中通过 providers: [UserService] 来注册 UserService 服务。以上代码成功运行后,页面的显示结果如下:

ng-provider-scope

当点击 “改名” 按钮之后,你会发现名字从 semlinker 变化成 lolo。这表示这两个模块之间是共享同一个 UserService 实例。

为什么会这样呢?因为在编译阶段,非懒加载的特性模块 UserModule 中配置的 providers 会与 AppModule 中配置的 providers 进行合并,当发现使用同样的 Token 时,AppModule 中配置的 provider 会生效,此后 Angular 会根据合并的 providers 创建根级注入器。此外,当我们导入的两个模块中,共用同一个 Token 来配置 provider, 后面导入的模块将会生效。

懒加载模块

估计有的小伙伴已经注意到了,我们在介绍前面的内容时,有强调非懒加载的特性模块,那么对于懒加载的模块会是什么情况呢?我们马上先来更新一下上面的示例,把 UserModule 改为懒加载的特性模块。

app.module.ts

1
2
3
4
5
6
7
8
9
10
11
12
import {RouterModule} from '@angular/router';

@NgModule({
imports: [BrowserModule, RouterModule.forRoot([{
path: 'user',
loadChildren : './user/user.module#UserModule'
}])
],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule { }

app.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component({
selector: 'my-app',
template: `
<p>
我是 {{userName}} ——(AppModule)
</p>
<button routerLink='user'>加载用户模块</button>
<router-outlet></router-outlet>
`
,
})
export class AppComponent {
constructor(private userService: UserService) { }

get userName() {
return this.userService.name;
}
}

user.module.ts

1
2
3
4
5
6
7
8
9
10
11
12
@NgModule({
imports: [
CommonModule,
RouterModule.forChild([{
path:'',component: UserComponent
}])
],
declarations: [UserComponent],
providers: [UserService],
exports: []
})
export class UserModule { }

在页面成功运行后,点击 “改名” 后,你会发现 AppModule 内的名字没有发生改变,具体如下图所示:

ng-provider-scope-lazy

为什么懒加载的模块与非懒加载的模块会产生不一样的结果呢?这是因为对于懒加载的模块来说,它会基于模块内配置的 providers 创建一个子注入器,以上面的示例来说,就是在 UserModule 中获取 UserService 服务时,会创建一个新的 UserService 实例,而不会使用全局的 UserService 实例。

有兴趣的同学,可以直接浏览线上的示例 angular-provider-scope

总结

  1. 如果在多个特性模块中,使用同一个 token 注册 provider,只有最后一个模块中的注册的 provider 才会生效。
  2. 如果在多个特性模块中,使用同一个 token 注册 provider,此外在根模块中同样也注册了相同的 provider,只有根模块中注册的 provider 会被添加到根注入器中,此后所有的特性模块将会共享同一个实例。
  3. 当在懒加载的模块中使用模块外的服务时,它将使用根注入器创建的服务实例。但如果已经在懒加载模块中注册了 provider,在模块内获取对应的服务时,它将从模块的子注入器中获取对应的服务实例。
  4. 除了在 NgModule 中配置 provider 之外,我们也可以通过 @Component metadata 对象的 providers 属性配置独立的服务。

欢迎小伙伴们订阅前端修仙之路,及时阅读 Angular、RxJS、TypeScript 和 Node.js 最新文章。

qrcode