问题一:如何引用私有库
起初认为既然node_modules
是用来存放安装的包的,那么直接把 @yunzhi
放进node_modules
里就可以了。
尝试之后发现,虽然可以使用库中的函数,但是当直接引用函数时系统不自动引用@yunzhi
,需要人工手动引入。
询问之后发现只需要根据引用的文件名在package.json
中引入相应依赖,再重新执行npm install
即可。
之后又发现package.json中主要包含以下几种参数
"scripts":{...}
"dependencies":{...}
"devDependencies":{...}
其中 scripts:
定义了一组可以运行的 node 脚本。
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
}
也就是平常用到的ng s,ng t等。
dependencies
设置了作为依赖安装的 npm
软件包的列表。
比如:
"@angular/animations": "~12.1.2",
"@angular/common": "~12.1.2",
"@angular/compiler": "~12.1.2",
"@angular/core": "~12.1.2",
"@angular/forms": "~12.1.2",
"@yunzhi/ng-common": "0.0.6",
"@yunzhi/ng-mock-api": "0.0.9",
"@yunzhi/ng-theme": "0.0.8",
"@yunzhi/utils": "0.0.3"
这些都是为我们的应用实际提供相应功能的依赖,也就是为MVC层提供服务的依赖。
devDependencies
设置了作为开发依赖安装的 npm
软件包的列表。
比如:
"karma": "~5.1.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.0.3",
"karma-jasmine": "~4.0.0",
"@angular/cli": "~12.1.2",
"@angular/compiler-cli": "~12.1.2",
"@types/jasmine": "~3.6.0",
"@types/node": "^12.11.1",
"@yunzhi/ng-router-testing": "0.0.2",
这些依赖都是在开发时用于测试时所需要的依赖,实际产品中用户并不会用到这些依赖。
知道了这些以后对于如何更改angular版本也就变得很简单,我们只需要改动带有@angular的依赖的版本即可。
问题二:在添加mockApi时遇到的问题
对于目前的使用来说只需要添加 qpis.ts
, api.testing.modules
, api.demo.modules
。
其中 apis.ts
也就是一个包含有我们所要用到的API的数组。
export const apis = [
UserApi,
LogApi,
ClientApi
];
api.demo.module
用于ng s下脱离后台跑demoapi.testing.module
用于进行单元测试。
providers: [
{
provide: HTTP_INTERCEPTORS, multi: true, useClass: MockApiTestingInterceptor.forRoot(apis)
}]
这是api.testing中的主要内容,通过MockApiTestingInterceptor
的forRoot
方法提供了HTTP_INTERCEPTORS
拦截器服务。
Angular在发起Http请求时,会查询当前模块是否有HTTP_INTERCEPTORS的提供者, 如果有则会使用该提供者对请求进行拦截。
使用multi: true
来表时当前提供者为多个具有这种能力的一个(使用该方案保证了可以定义多个拦截器)。
static forRoot(mockApis: Type
这是MockApiTestingInterceptor
中的forRoot
方法,接收一个API数组,返回一个HttpInterceptor
。
如图便是MockApi的机制
返还了HTTP_INTERCEPTORS
拦截器服务,拦截到请求后就会根据forRoot
方法中的api
数组寻找相应的api
.
Api大致格式
export class XxxApi implements MockApiInterface {
protected url = 'xxx'
getInjectors(): ApiInjector[] {
return [{
method: '',
url: '',
result: (urlMatches: string[], options: { params: HttpParams; }){}
}]
}
}
其中urlMatches为请求URL的相关信息,options包括了接受的参数等信息。
至此,便可以实现拦截请求并返回模拟数据的功能。
这是就又出现了一个问题,为什么要在ng s中模拟返回数据不能像ng t中一样只添加一个MockApiTestingInterceptor
提供的拦截器
于是我尝试着在app.modules中引用ApiTestingModule
不引用ApiDemoModule
,测试后发现出现了这样的报错 ——引用错误:beforeAll没有被定义。
main.js:1 Uncaught ReferenceError: beforeAll is not defined
ApiDemoModule:
providers: [
{
provide: HTTP_INTERCEPTORS, multi: true, useClass: LoadingInterceptor
},
{
provide: HTTP_INTERCEPTORS, multi: true, useClass: MockApiInterceptor.forRoot(apis)
}]
它比ApiTestingModule多了一个LoadingInterceptor
拦截器:
export class LoadingInterceptor implements HttpInterceptor {
public static loadingSubject = new Subject();
public static loading$ = LoadingInterceptor.loadingSubject.asObservable();
public static ignoreKey = 'loading-ignore';
intercept(req: HttpRequest, next: HttpHandler): Observable> {
if (req.params.has(LoadingInterceptor.ignoreKey)) {
return next.handle(req);
} else {
LoadingInterceptor.loadingSubject.next(true);
return next.handle(req).pipe(finalize(() => LoadingInterceptor.loadingSubject.next(false)));
}
}
}
其中 Subject 是一个特殊的Observable,它支持将值传给其他很多的Observable,
req.params.has(xxx)是用来检索req.params中是否含有xxx,如果没有则返回null,
handel()将HttpRequest转换为HttpEvents流,使其传向下一个拦截器
finalize返回一个Observable,该Observable镜像源Observable,但当源在完成或出错时终止时将调用指定函数。
之后经过测试在ng s中触发的是
return next.handle(req).pipe(finalize(() => LoadingInterceptor.loadingSubject.next(false)));
也就是说如果请求的参数含有loading-ignore
说明没有错误,可以直接传向下一个拦截器,如果不含有则说明有错误,需要经过finalize
管道的处理,返回一个执行时没有错误的Observable给下一个拦截器。