服务与DI简介
服务是一个广义的概念,它包括应用所需的任何值、函数或特性。狭义的服务是一个明确定义了用途的类。它应该做一些具体的事,并做好。
Angular 把组件和服务区分开,以提高模块性和复用性。
- 通过把组件中和视图有关的功能与其他类型的处理分离开,你可以让组件类更加精简、高效。 理想情况下,组件的工作只管用户体验,而不用顾及其它。 它应该提供用于数据绑定的属性和方法,以便作为视图(由模板渲染)和应用逻辑(通常包含一些模型的概念)的中介者。
- 组件不应该定义任何诸如从服务器获取数据、验证用户输入或直接往控制台中写日志等工作。 而要把这些任务委托给各种服务。通过把各种处理任务定义到可注入的服务类中,你可以让它被任何组件使用。 通过在不同的环境中注入同一种服务的不同提供商,你还可以让你的应用更具适应性。
Angular 不会强制遵循这些原则。它只会通过依赖注入让你能更容易地将应用逻辑分解为服务,并让这些服务可用于各个组件中。
服务范例
下面是一个服务类的范例,用于把日志记录到浏览器的控制台:
src/app/logger.service.ts (class)
content_copyexport class Logger {
log(msg: any) { console.log(msg); }
error(msg: any) { console.error(msg); }
warn(msg: any) { console.warn(msg); }
}
服务也可以依赖其它服务。比如,这里的 HeroService
就依赖于 Logger
服务,它还用 BackendService
来获取英雄数据。BackendService
还可能再转而依赖 HttpClient
服务来从服务器异步获取英雄列表。
src/app/hero.service.ts (class)
content_copyexport class HeroService {
private heroes: Hero[] = [];
constructor(
private backend: BackendService,
private logger: Logger) { }
getHeroes() {
this.backend.getAll(Hero).then( (heroes: Hero[]) => {
this.logger.log(`Fetched ${heroes.length} heroes.`);
this.heroes.push(...heroes); // fill cache
});
return this.heroes;
}
}
依赖注入(dependency injection)
组件是服务的消费者,也就是说,你可以把一个服务注入到组件中,让组件类得以访问该服务类。
在 Angular 中,要把一个类定义为服务,就要用 @
Injectable
装饰器来提供元数据,以便让 Angular 可以把它作为依赖注入到组件中。
同样,也要使用 @
Injectable
装饰器来表明一个组件或其它类(比如另一个服务、管道或 NgModule)拥有一个依赖。 依赖并不必然是服务,它也可能是函数或值等等。
依赖注入(通常简称 DI)被引入到 Angular 框架中,并且到处使用它,来为新建的组件提供所需的服务或其它东西。
- 注入器是主要的机制。你不用自己创建 Angular 注入器。Angular 会在启动过程中为你创建全应用级注入器。
- 该注入器维护一个包含它已创建的依赖实例的容器,并尽可能复用它们。
- 提供商是一个创建依赖的菜谱。对于服务来说,它通常就是这个服务类本身。你在应用中要用到的任何类都必须使用该应用的注入器注册一个提供商,以便注入器可以使用它来创建新实例。
当 Angular 创建组件类的新实例时,它会通过查看该组件类的构造函数,来决定该组件依赖哪些服务或其它依赖项。 比如 HeroListComponent
的构造函数中需要 HeroService
:
src/app/hero-list.component.ts (constructor)
content_copyconstructor(private service: HeroService) { }
当 Angular 发现某个组件依赖某个服务时,它会首先检查是否该注入器中已经有了那个服务的任何现有实例。如果所请求的服务尚不存在,注入器就会使用以前注册的服务提供商来制作一个,并把它加入注入器中,然后把该服务返回给 Angular。
当所有请求的服务已解析并返回时,Angular 可以用这些服务实例为参数,调用该组件的构造函数。
HeroService
的注入过程如下所示:
提供服务
对于要用到的任何服务,你必须至少注册一个提供商。服务可以注册自己的提供商,这样可以让自己到处可用。或者,你也可以为特定的模块或组件注册提供商。要注册提供商,就要在服务的 @
Injectable
装饰器中提供它的元数据,或者在@
NgModule
或 @
Component
的元数据中。
- 默认情况下,Angular CLI 的
ng generate service
命令会在@
Injectable
装饰器中提供元数据,把它注册到根注入器中。本教程就用这种方法注册了 HeroService 的提供商:
content_copy@Injectable({
providedIn: 'root',
})
当你在根一级提供服务时,Angular 会为 HeroService 创建一个单一的共享实例,并且把它注入到任何想要它的类中。这种在 @
Injectable
元数据中注册提供商的方式还让 Angular 能够通过移除那些从未被用过的服务来优化大小。
- 当你使用特定的 NgModule 注册提供商时,该服务的同一个实例将会对该 NgModule 中的所有组件可用。要想在这一层注册,请用
@
NgModule
装饰器中的providers
属性:
content_copy@NgModule({
providers: [
BackendService,
Logger
],
...
})
- 当你在组件级注册提供商时,你会为该组件的每一个新实例提供该服务的一个新实例。 要在组件级注册,就要在
@
Component
元数据的providers
属性中注册服务提供商。
src/app/hero-list.component.ts (component providers)
content_copy@Component({
selector: 'app-hero-list',
templateUrl: './hero-list.component.html',
providers: [ HeroService ]
})
要了解更多细节,请参见依赖注入一节。
本文档系腾讯云开发者社区成员共同维护,如有问题请联系 cloudcommunity@tencent.com