Angular7知识点8----服务提供商
这一章我们来了解一下依赖注入系统中一个很重要的概念——服务提供商,有人使用服务,那么就应该有人提供服务吧!这样理解有没有简单些,实际上,就是这样的!(等阅读完这一节你就会明白)
心法篇 详细教程篇
1、服务提供商的原型
服务提供商是一个对象,它会告诉怎样来找到对应的服务(通过DI令牌)、以及如何创建服务实例,不知道你是否还记得——不管是组件元数据对象的属性还是模块元数据对象的属性,对的描述都是:表示服务提供商列表!!!这时候,你可能会反驳了,怎么前面一章“依赖注入系统之服务”其中的元数据对象的数组中就只有一个类名,难道这就是服务提供商?没错,就是服务提供商,只不过是这种服务提供商的简写形式,现在我们来解开服务提供商的神秘面纱
服务提供商的原型
{provide: DI令牌, useClass/useExisting/useValue/useFactory: 服务类名}
吊炸天是吧!而下面的两种写法是等价的
providers:[LoggerService]
<=>
providers:[{provider: LoggerService, useClass: LoggerService}]
现在是不是对前一章的那种写法有清晰的认识了呢!现在。我们只需要记住这个原型即可,在记住这个原型后,请继续往下看
2、DI令牌
前面多次讲到,DI令牌是用来查找服务的,那么,我们怎样使用呢?或许,你应该看到这样的写法
constructor(private loggerService: LoggerService) {}
告诉你一个小秘密,DI令牌就藏在其中,而数组中的内容是这样的
providers: [LoggerService]
或许你已经猜到!没错这里的DI令牌就是,所以下面这段话很重要——写在构造函数中的那个你以为是服务类的类名其实是DI令牌!这个例子其实并没有将这段话的意思表达的淋漓尽致,请继续往下看(这个类用来创建DI令牌),在这里我们只需要有这么一个印象就好了
3、服务提供商
这或许是最近经常使用的,也最简单的服务提供商了,在前面你也接触过,在这里就不多讲了,但需要注意的是
providers:[LoggerService]
会帮我们new出一个的实例
4、服务提供商
或者你也可以叫它别名提供商,为什么这么叫呢,从字面上来说是“使用存在"的意思,其实没毛病,就是使用存在的服务实例!官方有一个很好的例子,我在这里引用一下:别名提供商
想象有这样的一个场景:原本应用中有一个名叫的服务类,现在我们需要使用一个新的名叫的服务。而这两个服务类的接口相同,出于某种原因,我们没法修改老的组件来使用,而你的想法是:当来组件使用来记录信息时,你希望它使用的是的实例而不是的实例,为了达到这个目的,我们就可以使用这种类别的提供商,代码如下
providers: [ NewLoggerService, { provide: OldLoggerService, useExisting: NewLoggerService}]
使用第二个服务提供商
constructor(private oldLoggerService: OldLoggerService)
代码解析
5、
在讲解下一种服务提供商之前,我们现在了解一下这个类(因为下一种服务提供商需要用到它),它是用来手动创建DI令牌的!下面通过例子来详细了解一下
1)、新建一个项目,取名test--
ng new test-teaching-provider
并cd进这个文件目录
2)、新建一个名叫.ts的文件,内容如下
import { InjectionToken } from "@angular/core";export const MY_DI_TOKEN = new InjectionToken('test useVaule');
现在我们就有了一个名叫的DI令牌,那么,这个令牌的使用我将和下一小节()一起讲解
6、服务提供商
你也可以叫它值提供商,在上一章的心法篇,我也说过,服务有很多种类,现在,我们利用就来提供一个类型的服务(连接上一小节)
在上一小节中我们创建了一个名叫的DI令牌,现在来使用它,代码如下
1)、新建一个名叫test--的项目
ng new test-teaching-privider
并cd进该文件目录
2)、启动应用
ng serve -o
现在你的应用应该是这个样子的
现在我们来使用来更改标题(红色框中的内容)
3)、修改的内容如下
import { Component, Inject } from '@angular/core';
import { MY_DI_TOKEN } from './assist';@Component({selector: 'app-root',templateUrl: './app.component.html',styleUrls: ['./app.component.css'],providers: [{ provide: MY_DI_TOKEN, useValue: 'test useValue' }] // 注册值提供商
})
export class AppComponent {title = 'test-teaching-provider';constructor(@Inject(MY_DI_TOKEN) myTitle: string) {// 使用值提供商this.title = myTitle;}}
现在你的界面应该变成了这样
是不是和代码中的这个字符串一样啊
代码解析
7、服务提供商
现在有这样一个场景:你新建了一个名叫hero-..ts的服务文件,而它的内容如下
import { Injectable } from '@angular/core';
import { LoggerService } from './logger.service';@Injectable({providedIn: 'root'
})
export class HeroDetailService {constructor(private loggerService: LoggerService,private auth: boolean) { }getStr() {return 'test useFactory successfully and ' + this.loggerService.getLog();}
}
注意,其中是一个服务类(也就是在服务类中使用其他服务,我前面也说过,和在组件中使用服务的方式是一样的),现在的问题是:这个服务的构造函数中多了另外一个类型的参数,那么,如果是你,你会怎样正确使用该服务,(学到这里,我想大多数人会按照如下的步骤进行),为了清晰起见,我们从头开始
1)、新建项目名叫test--,并cd进该项目
ng new test-teaching-provider
2)、新建一个名叫的服务
ng generate service logger
其内容如下
import { Injectable } from '@angular/core';@Injectable({providedIn: 'root'
})
export class LoggerService {constructor() { }getLog() {return 'use LoggerService in HeroDetailService';}
}
可以看出,这个服务类的作用是为了说明如果在服务中使用另外一个服务
3)、再新建一个名叫hero-的服务
ng generate service hero-detail
修改其内容如下
import { Injectable } from '@angular/core';
import { LoggerService } from './logger.service';@Injectable({providedIn: 'root'
})
export class HeroDetailService {constructor(private loggerService: LoggerService,private auth: boolean) { }getStr() {return 'test useFactory successfully and ' + this.loggerService.getLog();}
}
可以发现,这个服务类我是为了测试,如果测试成功,那么期望的结果是界面显示:test in
4)、删除.html默认内容,修改为一下内容
{{ str }}
5)、修改.ts文件内容为
import { Component, Inject, OnInit } from '@angular/core';
import { HeroDetailService } from './hero-detail.service';@Component({selector: 'app-root',templateUrl: './app.component.html',styleUrls: ['./app.component.css'],providers: [HeroDetailService] // 注册组件级别的服务提供商
})
export class AppComponent implements OnInit {title = 'test-teaching-provider';str: stringconstructor(private heroDetailService: HeroDetailService) {// 注入HeroDetailService}ngOnInit() {this.str = this.heroDetailService.getStr();}}
现在,你应该可以启动应用,如果不出意外的话,应该是期望的结果,可是,意外就此发生,浏览器的调试界面出现了下面的错误
(No for ),可能你也想到了,问题就出在砸门新增的上面,没错,不知道你是否还记得(我在这一章心法篇讲过的一句话)
现在你应该恍然大悟,看到的构造函数的两个参数
这时,你应该想到一种更正的方法,参见服务提供商这一小节,好,现在我们就来改一改
修改步骤如下
1)、新建.ts文件,内容如下
import { InjectionToken } from "@angular/core";export const MY_DI_TOKEN = new InjectionToken('');
2)、修改的内容如下
import { Injectable, Inject } from '@angular/core';
import { LoggerService } from './logger.service';
import { MY_DI_TOKEN } from './assist';@Injectable({providedIn: 'root'
})
export class HeroDetailService {constructor(private loggerService: LoggerService,@Inject(MY_DI_TOKEN) private auth: boolean) { }getStr() {return 'test useFactory successfully and ' + this.loggerService.getLog();}
}
3)、还有一步,别忘了为注册服务提供商(我们在.ts中为其注册)
providers: [HeroDetailService, { provide: MY_DI_TOKEN, useValue: true }] // 注册组件级别的服务提供商
现在,你的界面应该出现了期望的结果
如果你能自己做到这一步,说明前面的知识你已经掌握的足够好了!!
那么,你可能会问了,讲了这么多,额。。。。好像还没有涉及到。。。是的,接下来我们用一种更好的方式解决上面问题——,工厂函数,前面我应该也说过,我们可以直接提供一个服务实例,而不需要帮我们new,回到该项目修改以前(也就是报错的时候,我们不使用来解决此问题)使用的步骤如下
1)、新建一个.ts文件,内容如下
import { LoggerService } from "./logger.service";
import { HeroDetailService } from "./hero-detail.service";export const factoryFun = (loggerService: LoggerService)=>{return new HeroDetailService(loggerService, true);
}
可以发现函数(注意,这里是箭头函数,不能使普通的js函数,具体原因我也不太清楚。。。不好意思,原来是电脑的问题,电脑抽风了)返回一个的实例,
2)、修改.ts文件,使用该工厂函数
providers: [HeroDetailService, {provide: HeroDetailService, useFactory: factoryFun, deps:[LoggerService]}] // 注册组件级别的服务提供商
注意如下几点
现在,期望的结果又出现了
其实,在实际应用中,不会这样使用提供商,在这里我只是为了说明的使用方法,更好的例子见官方教程:
8、类接口
还有一个很重要的知识点——类接口,在中,类接口指的是:用作DI令牌的抽象类,因为本片教程已经很长了,就不在详细讲解了,请直接看官方教程:类-接口
问题篇
不知道你现在有没有清楚下面三者的关系
[provide: a, _: b] // 其中_表示我并不关心它的值(他可能是useClass/useValue/useExisting/useFactory中的某一个)constructor(private c: a){ }
也就是a、b、c三者的关系,如果你觉得自己清楚了,那么请告诉我,下面代码期望的结果是什么?
1)、内容如下
import { Injectable } from '@angular/core';@Injectable({providedIn: 'root'
})
export class LoggerService {constructor() { }getLog() {return 'logger';}
}
2)、内容如下
import { Injectable, Inject } from '@angular/core';@Injectable({providedIn: 'root'
})
export class HeroDetailService {constructor() { }getLog() {return 'heroDetail';}
}
3)、.ts的内容如下
import { Component, OnInit } from '@angular/core';
import { HeroDetailService } from './hero-detail.service';
import { LoggerService } from './logger.service';@Component({selector: 'app-root',templateUrl: './app.component.html',styleUrls: ['./app.component.css'],providers: [{provide: HeroDetailService, useClass: LoggerService}] // 注册组件级别的服务提供商
})
export class AppComponent implements OnInit {title = 'test-teaching-provider';str: stringconstructor(private heroDetailService: HeroDetailService) {// 注入HeroDetailService}ngOnInit() {this.str = this.heroDetailService.getLog();}}
现在你来告诉我,str的值是多少?是,还是(如果不知道就先试一试)