六、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. 页面展示,控制台将输出拦截的信息,两个拦截器分别进入了两次,请求时一次,响应时一次

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CompletableFuture // todo

相比较所有代码都在主线程执行&#xff0c;使用Future的好处&#xff1a;利用服务器多核、并发的优势。 不足&#xff1a; 开启没有返回值的异步线程&#xff1a; 1、runAsync 使用lambda表达式&#xff1a; 开启有返回值的异步线程&#xff1a; 1、supplyAsync 异步任务中的…

css面试常考布局(圣杯布局、双飞翼布局、三栏布局、两栏布局、三角形)

两栏布局 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </head> &…

arcgisPro加载天地图(CGCS2000)影像

1、注册天地图账号&#xff1b; 2、申请key&#xff1b; 3、添加WMTS服务器。 这里已经办好了前两步&#xff0c;下面详细介绍最后一步。 添加WMTS服务器。 在天地图网站&#xff0c;找到如下页面&#xff0c; 复制网址&#xff0c;如&#xff1a;http://t0.tianditu.gov.cn…

继承(7)

大家好&#xff0c;今天我们继续来学习一下继承的知识&#xff0c;这方面需要大家勤动脑才能理解&#xff0c;那么我们来看。 1.9 protected关键字 在类和对象章节中&#xff0c;为了实现封装特性,java中引入访向限定符,主要限定:类或者类中成员能否在类外和其他包中被访问. …

ModuleNotFoundError: No module named ‘podm.metrics‘报错等解决方法

ModuleNotFoundError: No module named podm.metrics’报错等解决方法 podm.metrics 在运行时报错&#xff1a; ModuleNotFoundError: No module named ‘podm.metrics’ 安装了podm后还是报错 解决方法&#xff1a; 查看安装位置 查看podm的安装位置&#xff0c;并打开到该…

HDFS异构存储和存储策略

一、HDFS异构存储类型 1.1 冷、热、温、冻数据 通常&#xff0c;公司或者组织总是有相当多的历史数据占用昂贵的存储空间。典型的数据使用模式是新传入的数据被应用程序大量使用&#xff0c;从而该数据被标记为"热"数据。随着时间的推移&#xff0c;存储的数据每周…

sklearn-逻辑回归-制作评分卡

目录 数据集处理 分箱 分多少个箱子合适 分箱要达成什么样的效果 对一个特征进行分箱的步骤 分箱的实现 封装计算 WOE 值和 IV值函数 画IV曲线&#xff0c;判断最佳分箱数量 结论 pd.qcut 执行报错 功能函数封装 判断分箱个数 在银行借贷场景中&#xff0c;评分卡是…

中学综合素质笔记3

第一章职业理念 第三节 教师观 考情提示&#xff1a; 单选题材料分析题 学习要求&#xff1a; 理解、 识记、 运用 &#xff08;一&#xff09;教师职业角色的转变&#xff08;单选材料分析&#xff09; 从教师与学生的关系看——对学生 新课程要求教师应该是学生学习的引…

【Linux】设备驱动中的ioctl详解

在Linux设备驱动开发中&#xff0c;ioctl&#xff08;输入输出控制&#xff09;是一个非常重要的接口&#xff0c;用于用户空间应用程序与内核空间设备驱动之间进行通信。通过ioctl&#xff0c;应用程序可以发送命令给设备驱动&#xff0c;控制设备的行为或获取设备的状态信息。…

linux上使用cmake编译的方法

一、hello 例程仅基于一个cpp文件 C文件或工程进行编译时可以使用g指令&#xff08;需要对每一个程序和源文件分别使用g指令编译&#xff09;&#xff0c;当程序变大时&#xff0c;一个工程文件往往会包含很文件夹和源文件&#xff0c;这时我们需要的编译指令将越来越长&#…

(vue)el-table-column type=“selection“表格选框怎么根据条件添加禁选

(vue)el-table-column type"selection"表格选框怎么根据条件添加禁选 html <el-table:data"tableData"style"width: 100%"><el-table-columntype"selection"width"55":selectable"checkSelectable">…

linux nginx 安装后,发现SSL模块未安装,如何处理?

&#x1f468;‍⚕ 主页&#xff1a; gis分享者 &#x1f468;‍⚕ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕ 收录于专栏&#xff1a;运维工程师 文章目录 前言SSL模块安装 前言 nginx 安装后&#xff0c;发现SSL模块未安装&…

kubeneters-循序渐进Cilium网络(二)

文章目录 概要IP 地址配置接口配置解析结论 概要 接续前一章节&#xff0c;我们还是以这张图继续深入Cilium网络世界 IP 地址配置 通过检查 Kubernetes 集群的当前环境&#xff0c;可以获取实际的 IP 地址和配置信息。这些信息将被补充到之前的网络示意图中&#xff0c;以使…

虚拟机使用MQ及介绍

mq官网&#xff1a;https://www.rabbitmq.com 一、虚拟机与 MQ 的结合优势 隔离与安全&#xff1a;虚拟机为 MQ 的运行提供了一个独立的环境&#xff0c;与宿主机以及其他虚拟机相互隔离。这意味着即使 MQ 所在的虚拟机出现故障或遭受安全威胁&#xff0c;也不会直接影响到宿主…

比亚迪夏直插家用MPV腹地,“迪王”开启全面销冠新征程

文/王俣祺 导语&#xff1a;比亚迪前脚刚收获2024年的全面成功&#xff0c;后脚立刻就开始布局2025年的产品矩阵了。比亚迪夏的横空出世&#xff0c;看来家用MPV市场也要感受“迪王”的恐怖如斯了。 家用MPV市场的“意外之喜” 1月8日&#xff0c;比亚迪夏终于在万众瞩目之下…

c++入门之 命名空间与输入输出

1、命名空间 1.1使用命名空间的原因 先看一个例子&#xff1a; #include <iostream>int round 0;int main() {printf("%d", round);return 0; }请问&#xff0c;这个程序能跑起来吗&#xff1f; 答案是否定的 原因是&#xff0c;当我们想创建一个全局变量 …

php 使用simplexml_load_string转换xml数据格式失败

本文介绍如何使用php函数解析xml数据为数组。 <?php$a <xml><ToUserName><![CDATA[ww8b77afac71336111]]></ToUserName><FromUserName><![CDATA[sys]]></FromUserName><CreateTime>1736328669</CreateTime><Ms…

12 USART串口通讯

1 串口物理层 两个设备的“DB9接口”之间通过串口信号建立连接&#xff0c;串口信号线中使用“RS232标准”传输数据信号。由于RS232电平标准的信号不能直接被控制器直接识别&#xff0c;所以这些信号会经过“电平转换芯片”转换成控制器能识别的“TTL校准”的电平信号&#xff…

FreePBX 17 on ubuntu24 with Asterisk 20

版本配置&#xff1a; FreePBX 17&#xff08;最新&#xff09; Asterisk 20&#xff08;最新Asterisk 22&#xff0c;但是FreePBX 17最新只支持Asterisk 21&#xff0c;但是21非LTS版本&#xff0c;所以选择Asterisk 20&#xff09; PHP 8.2 Maria DB (v10.11) Node J…

搜广推面经五

饿了么推荐算法 一、介绍InfoNCE Loss、InfoNCE温度系数的作用 InfoNCE Loss&#xff08;Information Noise Contrastive Estimation Loss&#xff09;是一种常用于自监督学习和对比学习中的损失函数&#xff0c;特别是在信息论和无监督学习中有广泛应用。 它的核心思想是通过…