Services 是每个 Angular 应用程序的基本块之一。Service 是一个普通的 TypeScript 类,它也可以没有使用 @Injectable 装饰器。 比如下面是我们定义一个 UserService 类:
export class UserService {}
定义完 UserService 类之后,我们可以在 NgModule
中注册它:
import { UserService } from './user.service';
...
@NgModule({
imports: [ BrowserModule],
declarations: [ AppComponent],
bootstrap: [ AppComponent],
providers: [UserService]
})
在 Angular 6 之后,我们也可以利用 @Injectable
的元数据来配置服务类,如:
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
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
import { Injectable } from '@angular/core';
@Injectable({
providedIn: "root"
})
export class UserService {
name = "semlinker";
changeName() {
this.name = 'Lolo';
}
}
user.component.ts
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
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
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 服务。以上代码成功运行后,页面的显示结果如下:
当点击 “改名” 按钮之后,你会发现名字从 semlinker 变化成 lolo。这表示这两个模块之间是共享同一个 UserService 实例。
为什么会这样呢?因为在编译阶段,非懒加载的特性模块 UserModule 中配置的 providers 会与 AppModule 中配置的 providers 进行合并,当发现使用同样的 Token 时,AppModule 中配置的 provider 会生效,此后 Angular 会根据合并的 providers 创建根级注入器。此外,当我们导入的两个模块中,共用同一个 Token 来配置 provider, 后面导入的模块将会生效。
估计有的小伙伴已经注意到了,我们在介绍前面的内容时,有强调非懒加载的特性模块,那么对于懒加载的模块会是什么情况呢?我们马上先来更新一下上面的示例,把 UserModule 改为懒加载的特性模块。
app.module.ts
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
@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
@NgModule({
imports: [
CommonModule,
RouterModule.forChild([{
path:'',component: UserComponent
}])
],
declarations: [UserComponent],
providers: [UserService],
exports: []
})
export class UserModule { }
在页面成功运行后,点击 “改名” 后,你会发现 AppModule 内的名字没有发生改变,具体如下图所示:
为什么懒加载的模块与非懒加载的模块会产生不一样的结果呢?这是因为对于懒加载的模块来说,它会基于模块内配置的 providers 创建一个子注入器,以上面的示例来说,就是在 UserModule 中获取 UserService 服务时,会创建一个新的 UserService 实例,而不会使用全局的 UserService 实例。
有兴趣的同学,可以直接浏览线上的示例 angular-provider-scope。
@Component
metadata 对象的 providers 属性配置独立的服务。