六、Angular 发送请求/ HttpClient 模块

一、应用 HttpClient 模块

  • @angular/common/http 中的 HttpClient 类基于浏览器提供的 XMLHttpRequest 接口。
  • 要想使用 HtpClient 模块,就要先导入 Anqular 的 HttpClientModule。大多数 Web 应用程序都会在根模块 AppModule 中导入它。
  1. 编辑 src/app/app.module.ts,导入 HttpClientModule 模块,导入顺序要在 BrowserModule 之后

    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    //导入 httpClient
    import { HttpClientModule } from '@angular/common/http';import { AppComponent } from './app.component';@NgModule({declarations: [AppComponent],imports: [BrowserModule,HttpClientModule //导入 httpClient,注意要放在 BrowserModule 之后],providers: [],bootstrap: [AppComponent]
    })
    export class AppModule {}
    
  2. 通过构造函数将实例注册到类中

    import { Injectable } from '@angular/core';
    import { HttpClient } from '@angular/common/http';@Injectable()
    export class DemoService {constructor(private http: HttpClient) {}
    }
    

二、模拟后端接口(创建RESTful API 服务)

[1]. 使用 json-server 创建 RESTful API 服务

  • json-server 是一个 Node.js 模块,底层运行在 Express 服务器上,用户可以指定一个JSON 文件作为 RESTful API 服务的数据源。 使用json-server 在本地搭建一个JSON 服务器对外提供 RESTful API 服务。前端开发工程师在无后端的情况下,可以用它作为后端 RESTfulAPI 服务器。
  1. 全局安装 json-server
    node 版本超过 14 直接安装即可

    npm install -g json-server
    

    我的node版本为 12.11.0,所以选择固定 json-server 版本

    npm install -g json-server@0.17.4
    
  2. 在任意位置创建 data 目录,并创建 db.json 文件,内容如下

    {"data": [{"username": "张三","age": 15}]
    }
    
  3. 在 data 目录下打开命令行窗口, 输入如下指令启动 json-server

    json-server db.json
    

    控制台将会有如下信息

  4. 在浏览器中输入 http://localhost:3000/data

[2]. 使用 Angular 内存数据库模拟服务器

  • Angular 内存数据库基于in-memory-web-api库,该库用于 Angular 演示和测试时调用内存中的网络 API,可模仿RESTful API 服务上的 CRUD 增、、改、查操作。它拦截了 Angular的 HTTP 请求和HttpClient 请求,这些请求原本会发送到远程服务器,然后将它们重定向到定义的内存数据库中。
  • in-memory-web-api 库集成在 Angular 中,该库会替换 HttpClient 模块中的 HttpBackend服务,新的服务会模拟 RESTful 风格的后端的行为。
  • in-memory-web-api 库仅拦截了 Angular 中的 HTTP 请求,它实际上没有运行 Web服务器。因此我们不能通过浏览器或者其他 Angular 环境外的工具访问它的 RESTful API 资源。
  • in-memory-web-api 库所虚拟的 API位于内存中,这也就意味着当刷新浏览器后,所有的数据都会消失。
  • 使用 Angular 内存数据库的优势显而易见: 无须单独构建和启动测试服务器
  1. 在angualr 项目中安装 in-memory-web-api 库
    我的node版本为 12.11.0,所以选择固定 angular-in-memory-web-api 版本

    npm i angular-in-memory-web-api@0.10.0 --save
    
  2. src/app 目录下新建 In-mem-hero-service.ts 文件,内容如下

    import { InMemoryDbService } from 'angular-in-memory-web-api';export class InMemHeroService implements InMemoryDbService {// 创建模拟数据createDb() {// 变量名 heroes 将被视作 URL 的一部分let heroes = [{ id: 1, name: '张三' },{ id: 2, name: '李四' },{ id: 3, name: '王五' },{ id: 4, name: '赵六' },{ id: 5, name: '孙琦' }];return { heroes };}
    }
    
  3. src/app/app.module.ts 文件中导入 InMemHeroService 类

    • HttpClientInMemoryWebApiModule 的 forRoot 方法的可以提供第二个参数
      HttpclientInMemoryWebApiModule.forRoot(InMemHeroService, { delay: 0} ) // 无延迟
      HttpclientInMemoryWebApiModule.forRoot(InMemHeroService, { delay: 500 }) //延迟500ms
      
    • 默认情况下,HttpClientlnMemoryWebApiModule 模块会拦截所有的 HttpClient 请求在实际工作中,我们可能需要同时使用 HttpClient 模块和 HttpClientinMemoryWebApiModule模块,意思是同时访问外部和内存的RESTful API资源。这时,我们可以通过配置选项passThruUnknownUrl 来实现,具体代码如下。
      HttpClientInMemoryWebApiModule.forRoot(InMemHeroService,{passThruUnknownUrl: true})
      
    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';//导入 httpClient
    import { HttpClientModule } from '@angular/common/http';
    // 导入 HttpClientInMemoryWebApiModule 注册数据存储服务
    import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';import { AppRoutingModule } from './app-routing.module';
    import { AppComponent } from './app.component';// 导入自己创建的 InMemHeroService 类
    import { InMemHeroService } from './in-mem-hero-service';@NgModule({declarations: [AppComponent],imports: [BrowserModule,AppRoutingModule,HttpClientModule, //导入 HttpClientModule BrowserModule 之后HttpClientInMemoryWebApiModule.forRoot(InMemHeroService) //该模块必须在 HttpClientModule 之后],providers: [],bootstrap: [AppComponent]
    })
    export class AppModule {}
    
  4. 修改 src/app/app.components.ts 文件

    import { Component, OnInit } from '@angular/core';
    import { HttpClient } from '@angular/common/http';@Component({selector: 'app-root',template: ``,styles: []
    })
    export class AppComponent implements OnInit {constructor(private http: HttpClient) {}ngOnInit(): void {// 获取所有的数据this.http.get('api/heroes').subscribe((data) => {console.log(data);});// 获取id为1的数据this.http.get('api/heroes/1').subscribe((data) => {console.log(data); // {id:1, name: "张三"}});// 获取name以李开头的数据this.http.get('api/heroes?name=^李').subscribe((data) => {console.log(data); // [{id:2, name: "李四"}]});}
    }
    

三、从服务器获取数据

  • HttpClient 模块允许我们在调用 HTTP 请求时使用泛型,通过泛型告诉 Angular 我们期望从HTTP 请求获得的响应类型。响应的类型可以是 any 变量类型(如 string )、类或接口等。如下面的代码执行 HttpClient 模块的 GET 请求,将预期的响应类型指定为 Hero 对象的数组。
    export class Hero {constructor(public id = 1, public name = '') {}
    }this.http.get<hero[]>(this.hreoesUrl)
    
  • 指定响应类型是给 TypeScript 看的声明,并不能保证服务器会实际使用此类型的对象进行响应。服务器 API 返回的实际响应类型是由服务器来保证的。换句话说,用户可以对Hero 类中的属性随意定义。因此,服务器实际返回的对象与类的定义并没有直接关系。
  1. 新建一个项目 demo-http

    ng new demo-http -t -s --minimal
    
  2. 安装 Angular 内存数据库

    npm i angular-in-memory-web-api@0.10.0 -S
    
  3. 新建一个 hero 接口(位置:src/app/hero.ts)

    ng g interface hero
    
  4. 修改 hero 接口文件 (位置:src/app/hero.ts)

    export interface Hero {id: number;name: string;
    }
    
  5. 新建 inMemHero 服务(位置:src/app/in-mem-hero.service.ts)

    ng g s inMemHero
    
  6. 修改 inMemHero 服务文件(位置:src/app/in-mem-hero.service.ts)

    import { Injectable } from '@angular/core';
    import { InMemoryDbService, RequestInfo } from 'angular-in-memory-web-api';
    import { Observable } from 'rxjs';@Injectable()
    export class InMemHeroService implements InMemoryDbService {createDb(reqInfo?: RequestInfo): {} | Observable<{}> | Promise<{}> {// 变量名 heroes 将被视作 URL 的一部分let heroes = [{ id: 1, name: '张三' },{ id: 2, name: '李四' },{ id: 3, name: '王五' },{ id: 4, name: '赵六' },{ id: 5, name: '孙琦' }];return { heroes };}
    }
    
  7. 编辑 src/app/app.module.ts 文件

    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';//导入 httpClient
    import { HttpClientModule } from '@angular/common/http';
    // 导入 HttpClientInMemoryWebApiModule 注册数据存储服务
    import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';import { AppComponent } from './app.component';// 导入自己创建的 InMemHeroService 类
    import { InMemHeroService } from './in-mem-hero.service';@NgModule({declarations: [AppComponent],imports: [BrowserModule,HttpClientModule, //导入 HttpClientModule BrowserModule 之后HttpClientInMemoryWebApiModule.forRoot(InMemHeroService) //该模块必须在 HttpClientModule 之后],providers: [],bootstrap: [AppComponent]
    })
    export class AppModule {}
    
  8. 编辑 src/app/app.component.ts 组件

    import { HttpClient } from '@angular/common/http';
    import { Component, OnInit } from '@angular/core';
    import { Observable } from 'rxjs';
    import { Hero } from './hero';@Component({selector: 'app-root',template: `<div style="text-align: center;"><p *ngFor="let hero of heroes">{{ hero.id }}-{{ hero.name }}</p></div>`,styles: []
    })
    export class AppComponent implements OnInit {private heroesUrl = 'api/heroes';heroes: Hero[];constructor(private http: HttpClient) {}ngOnInit(): void {this.getHeroes().subscribe((data) => {console.log(data);this.heroes = data;});}getHeroes(): Observable<Hero[]> {return this.http.get<Hero[]>(this.heroesUrl);}
    }
    

四、HttpClient 模块的请求头配置

[1]. 添加请求头

HttpClient 方法的最后一个参数可以指定一个可选的配置对象,通过它可以对请求头进行配置。常见的配置有需要 Content-Type 标识来显式声明 HTTP 请求正文的 MIME 类型、权限认证中的Authorization 令牌以及 HTTP 请求中的参数传递等。

import { HttpClient } from '@angular/common/http';
import { HttpHeaders } from '@angular/common/http';export class DemoService {constructor(private http: HttpClient) {}getData() {const httpOptions = {headers: new HttpHeaders({'Content-Type': 'application/json'})};return this.http.get('api/heroes', httpOptions);}
}

[2]. 获取完整的响应信息

有时访问服务器,需要读取它返回的一个特殊的响应头或响应状态码,因此可能需要完整的响应信息,而不是只有响应体。在 HttpClient 模块的 get0)方法中,observe 选项可用来告诉 HttpClient 模块,希望服务器返回完整的响应信息,代码如下。

import { HttpClient } from '@angular/common/http';export class DemoService {constructor(private http: HttpClient) {}getData() {return this.http.get('api/heroes', { observe: 'response' });}
}

[3]. 配置请求参数

设置单个参数

import { HttpClient, HttpParams } from '@angular/common/http';export class DemoService {constructor(private http: HttpClient) {}searchHero(key: string) {const options = { params: new HttpParams().set('name', key) };return this.http.get('api/heroes', options);}
}

设置多个参数

new HttpParams().append('id', '1').append('name', '张三')

使用 fromString 变量通过 URL 查询字符串构建请求参数

new HttpParams({ fromString: 'id=1&name=张三'});

[4]. 请求非 JSON 数据

不是所有的 API 都会返回 JSON 数据。有时候它们会从服务器读取文本文件,并把文本文件的内容记录下来,然后把这些内容使用 Observable 的形式返回给调用者。我们可以通过在HttpClient 模块提供的 get() 方法中配置 responseType 选项来指定获取响应内容的类型。

this.http.get (filename, {responseType: 'text'})
.pipe(tap( data => console.log(filename, data))
);

五、HttpClient 模块与RxJS配合

[1]. 错误处理

  • 处理单个接口的错误:调用 HttpClient() 方法,返回可观察对象,可以在可观测对象的订阅方法中添加错误处理的逻辑代码:这种方式仅针对某个组件的接口,无法处理某一类错误

    // getHeroes(): Observable<Hero[]> {
    //   const httpOptions = {
    //     headers: new HttpHeaders({
    //       'Content-Type': 'application/json'
    //     })
    //   };//   return this.http.get<Hero[]>(this.heroesUrl);
    // }this.getHeroes().subscribe((data) => {console.log(data);this.heroes = data;},(error) => {console.log(error);}
    );
    
  • 在接口处理错误:由 HttpClient 方法返回的可观察对象通过管道传给错误处理器

    import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
    import { Observable, throwError } from 'rxjs';
    import { catchError, retry } from 'rxjs/operators';
    import { Hero } from './hero';export class DemoService {private heroesUrl = 'api/heroes';constructor(private http: HttpClient) {}getHeroes(): Observable<Hero[]> {return this.http.get<Hero[]>(this.heroesUrl).pipe(catchError(this.handleError) // 错误处理);}private handleError(error: HttpErrorResponse) {if (error.error instanceof ErrorEvent) {// 代码运行错误或网络错误console.error('代码运行错误或网络错误:', error.error.message);} else {// 服务器发生错误,返回一个不成功的响应代码console.error(`错误码是:${error.status}, 错误信息:${error.error}`);}return throwError('系统发生错误,请稍后重试');}
    }
    

[2]. 重试

RxJS 提供了几个 retry 操作符,它们可以对失败的可观察对象自动重新订阅几次,其中最简单的是 retry0)操作符。对调用 HttpClient 方法返回的结果进行重新订阅会导致重新发起 HTTP 请求。

import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, retry } from 'rxjs/operators';
import { Hero } from './hero';export class DemoService {private heroesUrl = 'api/heroes';constructor(private http: HttpClient) {}getHeroes(): Observable<Hero[]> {return this.http.get<Hero[]>(this.heroesUrl).pipe(retry(3), // 重试失败的请求,最多可重试3次catchError(this.handleError) // 错误处理);}private handleError(error: HttpErrorResponse) {if (error.error instanceof ErrorEvent) {// 代码运行错误或网络错误console.error('代码运行错误或网络错误:', error.error.message);} else {// 服务器发生错误,返回一个不成功的响应代码console.error(`错误码是:${error.status}, 错误信息:${error.error}`);}return throwError('系统发生错误,请稍后重试');}
}

六、把数据发送到服务器

[1]. 发送 POST 请求

handleError 可以看 第五章的错误处理

import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Hero } from './hero';export class DemoService {private heroesUrl = 'api/heroes';constructor(private http: HttpClient) {}addHero(hero: Hero): Observable<Hero> {const httpOptions = {headers: new HttpHeaders({'Content-Type': 'application/json'})};return this.http.post<Hero>(this.heroesUrl, hero, httpOptions);}
}

[2]. 发送 DELETE 请求

import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Hero } from './hero';export class DemoService {private heroesUrl = 'api/heroes';constructor(private http: HttpClient) {}deleteHero(hero: Hero | number): Observable<Hero> {const id = typeof hero === 'number' ? hero : hero.id;const url = this.heroesUrl + '/' + id;return this.http.delete<Hero>(url);}
}

[3]. 发送 PUT 请求

import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Hero } from './hero';export class DemoService {private heroesUrl = 'api/heroes';constructor(private http: HttpClient) {}updateHero(hero: Hero): Observable<Hero> {return this.http.put<Hero>(this.heroesUrl, hero);}
}

[4]. 用例

  1. 新建一个项目

    ng new demo-http -s -t --minimal
    
  2. 安装 Angular 内存数据库模拟服务器

    npm i angular-in-memory-web-api@0.10.0 --save
    
  3. 使用命令新建一个 hero 接口文件(位置src/app/hero.ts)

    ng g interface hero
    
  4. 修改 hero 接口文件 (位置src/app/hero.ts)

    export interface Hero {id: number;name: string;
    }
    
  5. 使用命令新建服务文件,用作请求的数据 (位置src/app/in-mem-hero.service.ts)

    ng g s inMemHero
    
  6. 修改服务文件 (位置src/app/in-mem-hero.service.ts)

    import { Injectable } from '@angular/core';
    import { InMemoryDbService, RequestInfo } from 'angular-in-memory-web-api';
    import { Observable } from 'rxjs';@Injectable()
    export class InMemHeroService implements InMemoryDbService {createDb(reqInfo?: RequestInfo): {} | Observable<{}> | Promise<{}> {// 变量名 heroes 将被视作 URL 的一部分let heroes = [{ id: 1, name: '张三' },{ id: 2, name: '李四' },{ id: 3, name: '王五' },{ id: 4, name: '赵六' },{ id: 5, name: '孙琦' }];return { heroes };}
    }
    
  7. 编辑 src/app/app.modulee.ts 模块文件

    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';import { AppComponent } from './app.component';
    import { ReactiveFormsModule } from '@angular/forms';
    import { HttpClientModule } from '@angular/common/http';
    import { InMemHeroService } from './in-mem-hero.service';
    import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';@NgModule({declarations: [AppComponent],imports: [BrowserModule,ReactiveFormsModule,HttpClientModule, // 须在 BrowserModule 后面HttpClientInMemoryWebApiModule.forRoot(InMemHeroService) // 须在 HttpClientModule 后面],providers: [],bootstrap: [AppComponent]
    })
    export class AppModule {}
    
  8. 使用命令新建服务,用于发送 http 请求(位置:src/app/hero.service.ts)

    ng g s hero
    
  9. 修改src/app/hero.service.ts 文件

    import { HttpClient, HttpHeaders } from '@angular/common/http';
    import { Injectable } from '@angular/core';
    import { Observable, of } from 'rxjs';
    import { tap, catchError } from 'rxjs/operators';
    import { Hero } from './hero';@Injectable({providedIn: 'root'
    })
    export class HeroService {// 内存数据库的 REST API 地址private herosUrl = 'api/heroes';// 请求头httpOptions = {headers: new HttpHeaders({'Content-Type': 'application/json'})};constructor(private http: HttpClient) {}// 用作处理请求产生的错误private handleError<T>(operation = 'operation', result?: T) {return (error: any): Observable<T> => {console.log(`${operation} 失败:${error.message}`);return of(result as T); // 返回可观察对象};}getHeroes(): Observable<Hero[]> {return this.http.get<Hero[]>(this.herosUrl).pipe(tap((_) => console.log('获取所有数据')),catchError(this.handleError<any>('getHeroes')));}addHero(hero: Hero): Observable<Hero> {return this.http.post<Hero>(this.herosUrl, hero, this.httpOptions).pipe(tap((newHero: Hero) => console.log(`添加的 hero 的id=${newHero.id}`)),catchError(this.handleError<Hero>('addHero')));}deleteHero(hero: Hero | number): Observable<Hero> {const id = typeof hero === 'number' ? hero : hero.id;const url = `${this.herosUrl}/${id}`;return this.http.delete<Hero>(url, this.httpOptions).pipe(tap((_) => console.log(`删除的 hero 的id=${id}`)),catchError(this.handleError<any>('deleteHero', hero)));}updateHero(hero: Hero): Observable<Hero> {hero.name = hero.name + (hero.id + 1);return this.http.put<Hero>(this.herosUrl, hero, this.httpOptions).pipe(tap((_) => console.log(`更新 hero id=${hero.id}`), catchError(this.handleError('updateHero', hero))));}
    }
    
  10. 编辑 src/app/app.component.ts 文件

    import { Component, OnInit } from '@angular/core';
    import { Hero } from './hero';
    import { FormBuilder, FormGroup, Validators } from '@angular/forms';
    import { HeroService } from './hero.service';@Component({selector: 'app-root',template: `<div><table><tr><th>ID</th><th>姓名</th><th>操作</th></tr><tr *ngFor="let hero of heroes"><td>{{ hero.id }}</td><td>{{ hero.name }}</td><td><button (click)="deleteHero(hero.id)">删除</button><button (click)="updateHero(hero)">更新</button></td></tr></table><br /><form [formGroup]="formGroup" (ngSubmit)="onSubmit()"><div class="block"><label>Id:</label><input type="text" formControlName="id" /></div><div class="block"><label>姓名:</label><input type="text" formControlName="name" /></div><input type="submit" value="添加" [disabled]="!formGroup.valid" /><br /><br />表单是否有效:{{ formGroup.valid }}<br />表单完整数据:{{ formGroup.valid | json }}<br /></form></div>`,styles: ['form { border: 1px solid red; } ', '.block label { display: inline-block; width: 50px; text-align: right; }']
    })
    export class AppComponent implements OnInit {heroes: Hero[];formGroup: FormGroup;constructor(private heroService: HeroService, private fb: FormBuilder) {}ngOnInit(): void {this.getHeroes();// 初始化表单this.formGroup = this.fb.group({id: this.fb.control('', Validators.required),name: this.fb.control('', Validators.required)});}getHeroes() {this.heroService.getHeroes().subscribe((data) => (this.heroes = data));}updateHero(hero: Hero) {this.heroService.updateHero(hero).subscribe((data) => {console.log('修改数据:', data);this.getHeroes();});}deleteHero(id: number) {this.heroService.deleteHero(id).subscribe((data) => {console.log('删除数据', data);this.getHeroes();});}onSubmit() {const hero = this.formGroup.value;hero.id = Number(hero.id);this.heroService.addHero(hero).subscribe((hero) => {if (hero) {this.getHeroes();} else {alert('发送错误');}this.formGroup.reset();});}
    }
  11. 页面如下

七、Angular 拦截器

Angular 中的拦截器(Httplnterceptor 接口)提供了一种拦截 HTTP 请求和 HTTP 响应的方法,可以用来监视与转换 HTTP 请求和 HTTP 响应。 拦截器使用一种常规的、标准的方法对每一次 HTTP 的请求和响应任务执行如认证、添加请求参数和记录日志等很多种隐式任务。 如果没有拦截器,那么开发者将不得不对 HttpClient 模块的每次调用显式地执行这些任务。

[1]. 创建拦截器

  • 要创建拦截器,就要创建一个实现了 Httplnterceptor 接口的类,并实现该接口中的 intercept()方法。用户可以使用如下的 Anqular CLI命令创建拦截器。
    ng generate interceptor <name>
    
  1. 新建一个拦截器 my,将生成 src/app/my.interceptor.ts 文件

    ng g interceptor my
    
  2. src/app/my.interceptor.ts 文件内容如下

    import { Injectable } from '@angular/core';
    import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
    import { Observable } from 'rxjs';@Injectable()
    export class MyInterceptor implements HttpInterceptor {constructor() {}/**** request: 请求对象实例* next: 拦截器链表中的下一个拦截器*/intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {// 输入请求信息,只有这一行是添加的,别的都是生成的console.log(JSON.stringify(request));// 继续向下走return next.handle(request);}
    }
    

[2]. 配置拦截器提供商

  • 在Angular 中配置提供商后,应用程序就可以使用提供商来配置注入器了。注入器负责提供依赖注入服务,进而 Web 应用程序就能使用依赖注入服务了。因此,在创建了拦截器后,我们还需要进一步配置拦截器提供商。

  • 由于拦截器是 HttpClient 服务的(可选)依赖,因此必须在提供 HttpClient 服务的同一个(或其各级父注入器)注入器中提供这些拦截器。我们在根模块 AppModule 中导入了HttoClientModule 模块,导致 Web 应用程序在其根注入器中提供了 HtpClient 服务,所以也要在根模块 AppModule 中提供这些拦截器。配置拦截器提供商的注册语句格式如下。

    @NgModule({providers: [{ provide: HTTP_INTERCEPTORS, useClass: MyInterceptor, multi: true }],
    })
    
  • 在上述代码中,我们在@NgModule() 装饰器的元数据的 providers 选项里配置拦截器提供商。其中 provide 选项值HTTP_INTERCEPTORS 常量来自 @angular/common/http 包; useClass选项值是我们创建的拦截器;multi 选项值为 true,表示当前注入的是一个数组的值,而不是单一的值,multi 选项值默认为 true。如果在 Web 应用程序中仅配置一个拦截器提供商,那么程序代码也可以直接写成如下形式。

    @NgModule({providers: [MyInterceptor],
    })
    

[3]. 用例

配置日志和错误信息的拦截器

  1. 新建一个项目

    ng new demo-http4 -s -t --minimal --routing=false --style=css
    
  2. 新建一个日志拦截器文件(位置:src/app/log.interceptor.ts)

    ng g interceptor log
    
  3. 修改日志拦截器文件(位置:src/app/log.interceptor.ts)

    import { Injectable } from '@angular/core';
    import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpResponse } from '@angular/common/http';
    import { Observable } from 'rxjs';
    import { tap, finalize } from 'rxjs/operators';@Injectable()
    export class LogInterceptor implements HttpInterceptor {constructor() {}intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {const started = Date.now();let ok: string;return next.handle(request).pipe(tap(// 正常是返回 HttpResponse 类型对象(event) => {console.log('进入Log 拦截器');ok = event instanceof HttpResponse ? 'succeeded' : '';},// 错误时返回 HttpErrorResponse 类型对象(error) => (ok = 'failed')),// 当 HTTP 请求调用完成或者有错误发生时执行下面的逻辑finalize(() => {const elapsed = Date.now() - started;const msg = `${request.method} "${request.urlWithParams}" ${ok} in ${elapsed} ms.`;console.log('Log拦截器' + msg); //输入请求信息}));}
    }
    
  4. 新建一个错误拦截器文件(位置:src/app/error.interceptor.ts)

    ng g interceptor error
    
  5. 修改错误拦截器文件(位置:src/app/error.interceptor.ts)

import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';@Injectable()
export class ErrorInterceptor implements HttpInterceptor {constructor() {}intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {return next.handle(request).pipe(tap((data) => console.log('进入 error 拦截器,没有发生错误', data),catchError((err) => {if (err.status === 401) {console.log('进入 error 拦截器,发生了 401 错误');}const error = err.error.message || err.statusText;return throwError(error);})));}
}
  1. 编辑 src/app/app.module.ts 文件

    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';import { HTTP_INTERCEPTORS } from '@angular/common/http';
    import { HttpClientModule } from '@angular/common/http';import { AppComponent } from './app.component';
    import { LogInterceptor } from './log.interceptor';
    import { ErrorInterceptor } from './error.interceptor';@NgModule({declarations: [AppComponent],imports: [BrowserModule, HttpClientModule],providers: [ // angular 会按照顺序依次进行拦截{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },{ provide: HTTP_INTERCEPTORS, useClass: LogInterceptor, multi: true }],bootstrap: [AppComponent]
    })
    export class AppModule {}
    
  2. 新建 user 接口文件(位置:src/app/user.ts)

    ng g interface user
    
  3. 修改 user 接口文件(位置:src/app/user.ts)

    export interface User {login: string;url: string;
    }
    
  4. 新建一个 github 服务类文件(位置:src/app/github.service.ts)

    ng g s github
    
  5. 修改 github 服务类文件(位置:src/app/github.service.ts)

    import { Injectable } from '@angular/core';
    import { HttpClient } from '@angular/common/http';
    import { Observable } from 'rxjs';
    import { User } from './user';@Injectable({providedIn: 'root'
    })
    export class GithubService {// github 的接口private usersUrl = 'https://api.github.com/users?since=1';constructor(private http: HttpClient) {}getUsers(): Observable<User[]> {return this.http.get<User[]>(this.usersUrl);}
    }
    
  6. 修改 src/app/app.component.ts 文件

    import { Component, OnInit } from '@angular/core';
    import { Observable } from 'rxjs';
    import { GithubService } from './github.service';
    import { User } from './user';@Component({selector: 'app-root',template: ` <div><h3>从github 上获取 users</h3><div *ngFor="let user of users$ | async"><strong>User Name: </strong>{{ user.login }} <strong>GitHub URL: </strong>{{ user.url }}</div></div>`,styles: []
    })
    export class AppComponent implements OnInit {users$: Observable<Array<User>>;constructor(private githubService: GithubService) {}ngOnInit(): void {this.users$ = this.githubService.getUsers();}
    }
    
  7. 页面展示,控制台将输出拦截的信息,两个拦截器分别进入了两次,请求时一次,响应时一次

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/505722.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于单片机的无线智能窗帘控制器的设计

摘 要 : 本文以单片机为控制核心 , 基于 PT2262/ 2272 无线收发模块 , 实现了窗帘的无线远程智能控制 . 该控制器通过高频无线收发模块实现了遥控窗帘的开合控制; 根据外部光线强弱实现自动开关窗帘 ; 根据设定时间自动完成开关过程; 通过语音播报当前环境温湿度信息以…

android刷机

android ota和img包下载地址&#xff1a; https://developers.google.com/android/images?hlzh-cn android启动过程 线刷 格式&#xff1a;ota格式 模式&#xff1a;recovery 优点&#xff1a;方便、简单&#xff0c;刷机方法通用&#xff0c;不会破坏手机底层数据&#xff0…

Vivado中Tri_mode_ethernet_mac的时序约束、分析、调整——(一)时序约束的基本概念

1、基本概念 推荐阅读&#xff0c;Ally Zhou编写的《Vivado使用误区与进阶》系列文章&#xff0c;熟悉基本概念、tcl语句的使用。 《Vivado使用误区与进阶》电子书开放下载&#xff01;&#xff01; 2、Vivado中的语法例程 1&#xff09;语法例程 约束的语句可以参考vivado…

设计模式 行为型 责任链模式(Chain of Responsibility Pattern)与 常见技术框架应用 解析

责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是一种行为型设计模式&#xff0c;它允许将请求沿着处理者链进行发送。每个处理者对象都有机会处理该请求&#xff0c;直到某个处理者决定处理该请求为止。这种模式的主要目的是避免请求的发送者和接收者之间…

ubuntu 20.04 安装docker--小白学习之路

更新包 sudo apt-get update # 安装需要的软件包以使apt能够通过HTTPS使用仓库 sudo apt-get install ca-certificates curl gnupg lsb-release 使用清华大学源 # 添加Docker官方的GPG密钥 curl -fsSL https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu/gpg | sudo…

Linux之线程池与单例模式

目录 线程池 线程池代码 单例模式 饿汉模式单例模式 懒汉模式单例模式 在前几期&#xff0c;我们已经学习了多线程的创建和控制&#xff0c;学习了多线程中的同步和互斥&#xff0c;学习了多线程中的条件变量和信号量&#xff0c;基于此我们实现了基于阻塞队列和基于环形队…

The Dedicated Few (10 player)

The Dedicated Few (10 player) 少数精锐&#xff08;10人&#xff09; &#xff1a;以少于9人的阵容击败纳克萨玛斯的所有首领&#xff08;10人&#xff09; 历时2小时做完了&#xff0c;不容易啊&#xff0c;别人可以的咱也可以。 World of Warcraft [CLASSIC][80猎人][G…

List ---- 模拟实现LIST功能的发现

目录 listlist概念 list 中的迭代器list迭代器知识const迭代器写法list访问自定义类型 附录代码 list list概念 list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。list的底层是双向链表结构&#xff0c;双向链表中每个元素…

vscode支持ssh远程开发

文章目录 一、生成ssh使用的公钥/密钥对二、使用vscode通过ssh连接服务器1.安装插件2.配置文件3.连接服务器4.新建文件夹&#xff0c;存放不同的任务 三、使用scp命令与服务器互传文件、文件夹1.检查Windows 系统是否支持scp命令2.在Windows系统本地的电脑向服务器传输文件、文…

Jmeter-压测时接口如何按照顺序执行

Jmeter-压测时接口如何按照顺序执行-临界部分控制器 在进行压力测试时&#xff0c;需要按照顺序进行压测&#xff0c;比如按照接口1、接口2、接口3、接口4 进行执行 查询结果是很混乱的&#xff0c;如果请求次数少&#xff0c;可能会按照顺序执行&#xff0c;但是随着次数增加…

day02-前端Web-JavaScript

目录 1. JS介绍2. 引入方式2.1 介绍2.2 演示 3. 基础语法3.1 书写规范3.2 变量3.2.1 let3.2.2 const3.2.3 注意 3.3 数据类型3.4 运算符3.4.1 运算符3.4.2 类型转换 3.5 流程控制语句 4. 函数4.1 格式一4.2 格式二 5. JS对象5.1 基本对象5.1.1 Array对象5.1.1.1 语法格式5.1.1.…

有收到腾讯委托律师事务所向AppStore投诉带有【水印相机】主标题名称App的开发者吗

近期&#xff0c;有多名开发者反馈&#xff0c;收到来自腾讯科技 (深圳) 有限公司委托北京的一家**诚律师事务所卞&#xff0c;写给AppStore的投诉邮件。 邮件内容主要说的是&#xff0c;腾讯注册了【水印相机】这四个字的商标&#xff0c;所以你们这些在AppStore上的app&…

2024年度漏洞态势分析报告,需要访问自取即可!(PDF版本)

2024年度漏洞态势分析报告&#xff0c;需要访问自取即可!(PDF版本),大家有什么好的也可以发一下看看

moviepy 将mp4视频文件提取音频mp3 - python 实现

DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;会员享有 百种数据集&#xff0c;持续增加中。 需要更多数据资源和技术解决方案&#xff0c;知识星球&#xff1a; “DataBall - X 数据球(free)” -------------------------------------------------------------…

算法(二)——一维差分、等差数列差分

文章目录 一维差分、等差数列差分一维差分例题&#xff1a;航班预订统计 等差数列差分例题&#xff1a;三步必杀例题&#xff1a;Lycanthropy 一维差分、等差数列差分 一维差分 差分解决的是 区间修改&#xff08;更新&#xff09;问题&#xff0c;特别是多次区间修改问题&…

深度学习笔记11-优化器对比实验(Tensorflow)

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 目录 一、导入数据并检查 二、配置数据集 三、数据可视化 四、构建模型 五、训练模型 六、模型对比评估 七、总结 一、导入数据并检查 import pathlib,…

FreeROTS学习 内存管理

内存管理是一个系统基本组成部分&#xff0c;FreeRTOS 中大量使用到了内存管理&#xff0c;比如创建任务、信号量、队列等会自动从堆中申请内存&#xff0c;用户应用层代码也可以 FreeRTOS 提供的内存管理函数来申请和释放内存 FreeRTOS 内存管理简介 FreeRTOS 创建任务、队列…

【设计模式】介绍常见的设计模式

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 文章目录 ✨ 介绍一下常见的设计模式✨ Spring 中常见的设计模式 这期内容主要是总结一下常见的设计模式&#xff0c;可…

6 分布式限流框架

限流的作用 在API对外互联网开放的情况下&#xff0c;是无法控制调用方的行为的。当遇到请求激增或者黑客攻击的情况下&#xff0c;会导致接口占用大量的服务器资源&#xff0c;使得接口响应效率的降低或者超时&#xff0c;更或者导致服务器宕机。 限流是指对应用服务进行限制…

【动态规划篇】欣赏概率论与镜像法融合下,别出心裁探索解答括号序列问题

本篇鸡汤&#xff1a;没有人能替你承受痛苦&#xff0c;也没有人能拿走你的坚强. 欢迎拜访&#xff1a;羑悻的小杀马特.-CSDN博客 本篇主题&#xff1a;带你解答洛谷的括号序列问题&#xff08;绝对巧解&#xff09; 制作日期&#xff1a;2025.01.10 隶属专栏&#xff1a;C/C题…