上节最后的错误是由于在教师列表组件中,使用了相对地址`/clazz/page`请求了后台数据,而不是绝对地址`http://angular.api.codedemo.club:81/clazz/page`,这样做最直接的好处就是不需要重复去写那个又臭又长的前缀了。 ## Api拦截器 前面我们已然接触了两个拦截器 ---- 用于与后台验证交互的`XAuthTokenInterceptor`以及在用于添加Api请求前缀的`ApiInterceptor`。 将`ApiInterceptor`添加到`AppModule`将使用该拦截器生效: ```typescript +++ b/first-app/src/app/app.module.ts @@ -15,6 +15,7 @@ import { SexPipe } from './personal-center/sex.pipe'; import {XAuthTokenInterceptor} from './x-auth-token.interceptor'; import {WelcomeComponent} from './welcome.component'; import { NavComponent } from './nav/nav.component'; +import {ApiInterceptor} from './api.interceptor'; @NgModule({ @@ -37,7 +38,8 @@ import { NavComponent } from './nav/nav.component'; RouterModule ], providers: [ - {provide: HTTP_INTERCEPTORS, useClass: XAuthTokenInterceptor, multi: true} + {provide: HTTP_INTERCEPTORS, useClass: XAuthTokenInterceptor, multi: true}, + {provide: HTTP_INTERCEPTORS, useClass: ApiInterceptor, multi: true} ], bootstrap: [IndexComponent] }) ``` ## 重复的轮子 `ApiInterceptor`被引用到`AppModule`后,重复的轮子应该到了退出历史舞台的时候了: 教师添加组件: ```typescript +++ b/first-app/src/app/add/add.component.ts @@ -26,7 +26,7 @@ export class AddComponent implements OnInit { onSubmit(): void { console.log(this.teacher); this.httpClient - .post('http://angular.api.codedemo.club:81/teacher', this.teacher) + .post('/teacher', this.teacher) .subscribe((result) => { console.log('接收到返回数据', result); this.router.navigate(['teacher']); ``` 教师列表: ```typescript +++ b/first-app/src/app/app.component.ts @@ -18,12 +18,12 @@ export class AppComponent implements OnInit { * 组件初始化完成后将被自动执行一次 */ ngOnInit(): void { - this.httpClient.get<Teacher[]>('http://angular.api.codedemo.club:81/teacher') + this.httpClient.get<Teacher[]>('/teacher') .subscribe(teachers => this.teachers = teachers); } onDelete(id: number): void { - const url = `http://angular.api.codedemo.club:81/teacher/${id}`; + const url = `/teacher/${id}`; ``` 编辑教师: ```typescript +++ b/first-app/src/app/edit/edit.component.ts @@ -21,7 +21,7 @@ export class EditComponent implements OnInit { const id = this.activeRoute.snapshot.params.id; // 拼接请求URL - const url = 'http://angular.api.codedemo.club:81/teacher/' + id; + const url = '/teacher/' + id; // 发起请求,成功时并打印请求结果,失败时打印失败结果 this.httpClient.get<Teacher>(url) .subscribe(data => this.teacher = data, @@ -32,7 +32,7 @@ export class EditComponent implements OnInit { onSubmit(): void { console.log(this.teacher); // 获取ID,拼接URL - const url = 'http://angular.api.codedemo.club:81/teacher/' + + const url = '/teacher/' + this.activeRoute.snapshot.params.id; // 发起请求,更新教师,成功时打印请求结果并刷新教师列表查看效果,失败时打印失败结果 this.httpClient.put(url, this.teacher) ``` 登录: ```typescript +++ b/first-app/src/app/login/login.component.ts @@ -36,7 +36,7 @@ export class LoginComponent implements OnInit { this.httpClient .get<Teacher>( - 'http://angular.api.codedemo.club:81/teacher/login', + '/teacher/login', {headers: httpHeaders}) .subscribe(teacher => this.beLogin.emit(teacher), error => { ``` 个人中心: ```typescript +++ b/first-app/src/app/personal-center/personal-center.component.ts @@ -14,7 +14,7 @@ export class PersonalCenterComponent implements OnInit { } ngOnInit(): void { - const url = 'http://angular.api.codedemo.club:81/teacher/me'; + const url = '/teacher/me'; this.httpClient.get<Teacher>(url) .subscribe(teacher => { console.log('请求当前登录用户成功'); ``` ### 单元测试 移除到重复的轮子后,移除项目所有的`fit`,接着使用`ng t`来测试项目已检测移除工作是否对历史测试造成了影响。打开控制台查看异常信息: ![image-20210408154942726](https://img.kancloud.cn/50/1b/501b3dedb10d4c9a94d36facf5d85c07_2198x164.png) 提示说没有找到`/teacher`请求,该请求发生于教师列表组件的功能中,找到相关的单元测试并加入`ApiInterceptor`: ```typescript +++ b/first-app/src/app/app.component.spec.ts @@ -1,7 +1,8 @@ import {TestBed} from '@angular/core/testing'; import {AppComponent} from './app.component'; -import {HttpClientModule} from '@angular/common/http'; +import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http'; import {RouterTestingModule} from '@angular/router/testing'; +import {ApiInterceptor} from './api.interceptor'; describe('AppComponent', () => { beforeEach(async () => { @@ -13,6 +14,9 @@ describe('AppComponent', () => { declarations: [ AppComponent ], + providers: [ + {provide: HTTP_INTERCEPTORS, useClass: ApiInterceptor, multi: true} + ], }).compileComponents(); }); ``` 其它错误: ![image-20210408155451423](https://img.kancloud.cn/48/5b/485bceb57b1a1c29d4650f8566de0784_1292x106.png) 前面两个错误,分别发生在登录组件: ```typescript +++ b/first-app/src/app/login/login.component.spec.ts @@ -5,6 +5,7 @@ import {FormsModule} from '@angular/forms'; import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http'; import {Teacher} from '../entity/teacher'; import {XAuthTokenInterceptor} from '../x-auth-token.interceptor'; +import {ApiInterceptor} from '../api.interceptor'; describe('LoginComponent', () => { let component: LoginComponent; @@ -18,7 +19,8 @@ describe('LoginComponent', () => { HttpClientModule ], providers: [ - {provide: HTTP_INTERCEPTORS, useClass: XAuthTokenInterceptor, multi: true} + {provide: HTTP_INTERCEPTORS, useClass: XAuthTokenInterceptor, multi: true}, + {provide: HTTP_INTERCEPTORS, useClass: ApiInterceptor, multi: true} ] }) ``` 以及个人中心组件上: ```typescript +++ b/first-app/src/app/personal-center/personal-center.component.spec.ts @@ -2,7 +2,8 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; import {PersonalCenterComponent} from './personal-center.component'; import {SexPipe} from './sex.pipe'; -import {HttpClientModule} from '@angular/common/http'; +import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http'; +import {ApiInterceptor} from '../api.interceptor'; describe('PersonalCenterComponent', () => { let component: PersonalCenterComponent; @@ -11,7 +12,10 @@ describe('PersonalCenterComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [PersonalCenterComponent, SexPipe], - imports: [HttpClientModule] + imports: [HttpClientModule], + providers: [ + {provide: HTTP_INTERCEPTORS, useClass: ApiInterceptor, multi: true} + ] }) .compileComponents(); }); ``` 此时请求的地址前缀正确,且状态码为`401`非`404`了: ![image-20210408160107902](https://img.kancloud.cn/ad/a8/ada81dc600d4888729933f3241bcb407_1440x72.png) `401`是用户在未登录的情况下获取登录用户发生在正常错误信息。 最后如果我们多刷新几次单元测试使用的浏览器,还可能得到如下错误: ![image-20210408160333686](https://img.kancloud.cn/27/39/2739843f6c268ce0a054b9c77fa5b2db_2106x354.png) 该错误是`TestBed`的一些惰性加载特性引发的。由于我们在不同的`TestBed`中以不同的方法提供了Http拦截器,不同的拦截器在`TestBed`使用时,被**混用**了。最终引发了上述错误。 这就像高峰期去食堂吃饭,乌压压的食堂中我刚刚看到了一个空座,当自己打完饭闭上眼走向那个座拉后,却不想该座位已经被同学占用了。追求根本的原因,是因为我们太懒,不愿意在走路的过程中反复地去更新座位的状态。我们在后续的章节中将专门的来讲解该问题产生的原因以及解决方案。 至此`ApiInterceptor`成功应用到了当前项目中。 ## 本节作业 在`ClazzModule`中添加其它路由,并完善各个组件间的跳转关系,使整个班级管理模块完整的工作起来。 ## 注意 本节在去轮子时修正的代码较多,如果这种变动不小心使你变的混乱,那么在继续学习以前请下载我们下述链接中提供的源码。 | 名称 | 链接 | | -------- | ------------------------------------------------------------ | | 本节源码 | [https://github.com/mengyunzhi/angular11-guild/archive/step6.6.2.zip](https://github.com/mengyunzhi/angular11-guild/archive/step6.6.2.zip) | | 所有源码 | [http://nas.yunzhi.club:5010/sharing/w3oUsMZ7j](http://nas.yunzhi.club:5010/sharing/w3oUsMZ7j) |