Angular http

     前端应用都需要通过 HTTP 协议与后端服务器通讯,@angular/common/http 中的 HttpClient 类为 Angular 应用程序提供的 API 来实现 HTTP 客户端功能。它基于浏览器提供的 XMLHttpRequest 接口。 HttpClient 带来的其它优点包括:可测试性、强类型的请求和响应对象、发起请求与接收响应时的拦截器支持,以及更好的、基于可观察(Observable)对象的 API 以及流式错误处理机制。

1.使用http服务

在根模块AppModule导入HttpClientModule。因为大多数应用中,多处多都要使用到http服务,所以直接导入到根模块去。

import { NgModule }         from '@angular/core';
import { BrowserModule }    from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [
    BrowserModule,
    HttpClientModule,
  ],
  declarations: [
    AppComponent,
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule {}

然后就可以在服务中使用http服务了

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class UserService {
  constructor(private http: HttpClient) { }

getUsers():Observable<Array<userInfo>>{
return this.http.get<Array<userInfo>>(url);
} }

2.封装httpService

   通常一个应用我们不会直接使用HttpClient,数据的访问没有这么简单,我们可能会对header 有统一的添加,错误处理,数据处理,网络异常等情况处理。如果不封装一个服务那么我们直接使用HttpClient,会出现大量的重复,冗余杂乱的代码,无法统一标准化,在长期的开发以及测试中会变得麻烦和增加维护成本。这就是为什么在一个应用程序中,无论项目多小,我们都要把数据访问封装一个单独的服务中去的原因。

这个是HttpClient.post 方法,我们可以看到他的返回类型是某个值的 RxJS Observable。

默认情况下把响应体当做无类型的 JSON 对象进行返回,如果指定了模版类型Array<userInfo>,他就会返回一个此类型的对象回来,但是具体还是得取决于数据接口返回来的数据。

参考上面的UserService服务里的getUsers。下面是某个组件调用。

 

users: Array<User>;

showUser() {
  this.userService.getUsers()
    .subscribe((data:Array<User>) => this.users = { ...data });
}

 

返回数据结果统一处理

通常我们API返回来的数据结构是统一的规范化结果,有利于项目前后端分离,团队合作开发。

export class ResultData<T> {
    success: boolean;
    msg?: string;
    data?: T;
    errorcode?: string;
    timestamp?: number;
}

例子在下面👇

错误处理

      当你发起一个网络请求,你不知道服务器会给你返回什么,你不知道请求是否能到达服务器,会不会出现网络堵塞,服务器错误等等等的问题,这时候就需要要捕获错误,并对错误进行处理。使用 RxJS 的 catchError() 操作符来建立对 Observable 结果的处理管道(pipe)

  //post 请求
public post(url: string, data = {}): Observable<any> { return this.http.post(url, data, httpOptions).pipe( map(this.extractData), catchError(this.handleError)//获取了由 HttpClient 方法返回的 Observable,并把它们通过管道传给错误处理器 ); }
// 返回结果
private extractData(res: Response) { return res as ResultData<any>; }
// 错误消息类
private handleError(error: HttpErrorResponse) { if (error.error instanceof ErrorEvent) { console.error('An error occurred:', error.error.message); } else { console.error( `Backend returned code ${error.status}, ` + `body was: ${error.error}` ); } return throwError('Something bad happened; please try again later.'); }

retry()

      有时候,错误只是临时性的,只要重试就可能会自动消失。RxJS 库提供了几个 retry 操作符,retry()可以对失败的 Observable 自动重新订阅几次。对 HttpClient 方法调用的结果进行重新订阅会导致重新发起 HTTP 请求。

  public post(url: string, data = {}): Observable<any> {
    return this.http.post(url, data, httpOptions).pipe(
      map(this.extractData),
      retry(3),//重试失败的请求,最多3次
      catchError(this.handleError)
    );
  }

添加请求头

    在向API发起请求很多需要添加额外的请求头。 比如,它们可能需要一个 Content-Type 头来显式定义请求体的 MIME 类型。 也可能服务器会需要一个认证用的令牌(token)。

HeroesService 在 httpOptions 对象中就定义了一些这样的请求头,并把它传给每个 HttpClient 的保存型方法。

    public getJsonHeader(header?: HttpHeaders): HttpHeaders {
        if (header == null) {
            header = new HttpHeaders();
        }
        return header.set('Content-Type', 'application/json')
.set('Authorization', 'Bearer ' + this.token); }
addUser (user: UserInfo): Observable<boolean> {
  return this.http.post<boolean>(url, user, this.getJsonHeader())
    .pipe(
      catchError(this.handleError('addUser', user))
    );
}

3.拦截请求

      http 拦截机制是 @angular/common/http 中的主要特性之一。  可以声明一些拦截器,用于监视和记录从应用发送到服务器的 HTTP 请求。 拦截器还可以用监视和转换从服务器返回到本应用的那些响应。实现拦截器,就要实现一个实现了 HttpInterceptor 接口中的 intercept() 方法的类。大多数拦截器拦截都会在传入时检查请求,然后把(可能被修改过的)请求转发给 next 对象的 handle() 方法,而 next 对象实现了 HttpHandler 接口。

import { HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>;
}
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpResponse } from '@angular/common/http';
import { finalize, tap } from 'rxjs/operators';
import { HttpInterceptor } from './HttpInterceptor';
import { LOGACTION } from '../types/model';
import { V5LogSQLService } from '../v5logSql.service';

@Injectable()
export class NoopInterceptor implements HttpInterceptor {

  constructor(private log: V5LogSQLService) { }
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    const startTime = Date.now();
    let status: number;

    return next.handle(req).pipe(
      tap(
        event => {
          if (event instanceof HttpResponse) {
            status = event.status;
          }
        },
        error => status = error.status
      ),
      finalize(() => {
        this.log.log(LOGACTION.HTTP, '' , JSON.stringify({ elapsedTime: (Date.now() - startTime) + 'ms', url: req.urlWithParams, status: status, param: req.body }));
      })
    );
  }
}

       next 对象表示拦截器链表中的下一个拦截器。 这个链表中的最后一个 next 对象就是 HttpClient 的后端处理器(backend handler),它会把请求发给服务器,并接收服务器的响应。大多数的拦截器都会调用 next.handle(),以便这个请求流能走到下一个拦截器,并最终传给后端处理器。 拦截器也可以不调用 next.handle(),使这个链路短路,并返回一个带有人工构造出来的服务器响应的 自己的 Observable,这是一种常见的中间件模式。

使用这个拦截器
      这个 NoopInterceptor 就是一个由 Angular 依赖注入 (DI)系统管理的服务。 像其它服务一样,你也必须先提供这个拦截器类,应用才能使用它。

由于拦截器是 HttpClient 服务的(可选)依赖,所以你必须在提供 HttpClient 的同一个(或其各级父注入器)注入器中提供这些拦截器。 那些在 DI 创建完 HttpClient 之后再提供的拦截器将会被忽略。由于在 AppModule 中导入了 HttpClientModule,导致本应用在其根注入器中提供了 HttpClient。所以你也同样要在 AppModule 中提供这些拦截器。

注意 multi: true 选项。 这个必须的选项会告诉 Angular HTTP_INTERCEPTORS 是一个多重提供商的令牌,表示它会注入一个多值的数组,而不是单一的值。

在从 @angular/common/http 中导入了 HTTP_INTERCEPTORS 注入令牌之后,编写如下的 NoopInterceptor 提供商注册语句:

AppModule根模块

{ provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true },

创建一个封装桶(barrel)文件,用于把所有拦截器都收集起来,一起提供给 httpInterceptorProviders 数组。

import { HTTP_INTERCEPTORS } from '@angular/common/http';

import { NoopInterceptor } from './noop-interceptor';

export const httpInterceptorProviders = [
  { provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true },
];

使用:

providers: [
  httpInterceptorProviders
],

      拦截请求还有,拦截的顺序,以及httpEvent等。 HttpRequest 和 HttpResponse是只读的,只可对其监视 or 日志记录,要想修改该请求,就要先克隆它,并修改这个克隆体,然后再把这个克隆体传给 next.handle(),你可以用一步操作中完成对请求的克隆和修改。具体看 https://angular.cn/guide/http#intercepting-requests-and-responses,因为我对这个不熟现实开发中并未使用,仅仅使用拦截器记录监听请求记录日志而已。 

     拦截器的使用场景有:设置默认请求头,记日志,缓存,返回多值可观察对象,监听进度事件等。

4.其他

1.前面提到过默认情况下把响应体当做无类型的 JSON 对象进行返回,如果请求返回来的是文件流等,就要另外做处理。

例如

getTextFile(filename: string) {
  return this.http.get(filename, {responseType: 'text'})
    .pipe(
      tap(
        data => this.log(filename, data),
        error => this.logError(filename, error)
      )
    );
}

2.RxJS 是一个库,用于把异步调用和基于回调的代码组合成函数式(functional)的、响应式(reactive)的风格。 很多 Angular API,包括 HttpClient 都会生成和消费 RxJS 的 Observable。在整个httpService中我们用了RxJS库提供的几个操作符 catchError Observable retry 等,学习RxJS,有利于开发者在整个应用程序开发的开发,可以提高效率,以及代码质量。

 

此随笔乃本人学习工作记录,如有疑问欢迎在下面评论,转载请标明出处。

如果对您有帮助请动动鼠标右下方给我来个赞,您的支持是我最大的动力。

posted @ 2020-01-26 16:32  huangenai  阅读(4122)  评论(1编辑  收藏  举报