激動人心的 Angular HttpClient

閱讀 Angular 6/RxJS 最新教程,請訪問 前端修仙之路

Angular 4.3.0-rc.0 版本已經發布?。在這個版本中,咱們等到了一個使人興奮的新功能 - HTTPClient API 的改進版本,之後媽媽不再用擔憂我處理 HTTP 請求了?。前端

HttpClient 是已有 Angular HTTP API 的演進,它在一個單獨的 @angular/common/http 包中。這是爲了確保現有的代碼庫能夠緩慢遷移到新的 API。

接下來讓咱們開啓 Angular 新版 Http Client 之旅。git

安裝

首先,咱們須要更新全部的包到 4.3.0-rc.0 版本。而後,咱們須要在 AppModule 中導入 HttpClientModule 模塊。具體以下:github

import { HttpClientModule } from '@angular/common/http';
@NgModule({
 declarations: [
   AppComponent
 ],
 imports: [
   BrowserModule,
   HttpClientModule
 ],
 bootstrap: [AppComponent]
})
export class AppModule { }

如今一切準備就緒。讓咱們來體驗一下咱們一直期待的三個新特性。typescript

特性一 默認 JSON 解析

如今 JSON 是默認的數據格式,咱們不須要再進行顯式的解析。即咱們不須要再使用如下代碼:json

http.get(url).map(res => res.json()).subscribe(...)

如今咱們能夠這樣寫:bootstrap

http.get(url).subscribe(...)

特性二 支持攔截器 (Interceptors)

攔截器容許咱們將中間件邏輯插入管線中。後端

請求攔截器 (Request Interceptor)

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

@Injectable()
class JWTInterceptor implements HttpInterceptor {
  
  constructor(private userService: UserService) {}
  
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    const JWT = `Bearer ${this.userService.getToken()}`;
    req = req.clone({
      setHeaders: {
        Authorization: JWT
      }
    });
    return next.handle(req);
  }
}

若是咱們想要註冊新的攔截器 (interceptor),咱們須要實現 HttpInterceptor 接口,而後實現該接口中的 intercept 方法。框架

export interface HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>;
}

須要注意的是,請求對象和響應對象必須是不可修改的 (immutable)。所以,咱們在返回請求對象前,咱們須要克隆原始的請求對象。ide

next.handle(req) 方法使用新的請求對象,調用底層的 XHR 對象,並返回響應事件流。函數

響應攔截器 (Response Interceptor)

@Injectable()
class JWTInterceptor implements HttpInterceptor {

  constructor(private router: Router) {}
  
  intercept(req: HttpRequest < any > ,
    next: HttpHandler): Observable < HttpEvent < any >> {

    return next.handle(req).map(event => {
        if (event instanceof HttpResponse) {
          if (event.status === 401) {
            // JWT expired, go to login
          }
        }
        return event;
      }
    }
}

響應攔截器能夠經過在 next.handle(req) 返回的流對象 (即 Observable 對象) 上應用附加的 Rx 操做符來轉換響應事件流對象。

接下來要應用 JWTInterceptor 響應攔截器的最後一件事是註冊該攔截器,即便用 HTTP_INTERCEPTORS 做爲 token,註冊 multi Provider:

[{ provide: HTTP_INTERCEPTORS, useClass: JWTInterceptor, multi: true }]

特性三 進度事件 (Progress Events)

進度事件能夠用於跟蹤文件上傳和下載。

import {
  HttpEventType,
  HttpClient,
  HttpRequest
} from '@angular/common/http';

http.request(new HttpRequest(
  'POST',
   URL,
   body, 
  {
    reportProgress: true
  })).subscribe(event => {

  if (event.type === HttpEventType.DownloadProgress) {
    // {
    // loaded:11, // Number of bytes uploaded or downloaded.
    // total :11 // Total number of bytes to upload or download
    // }
  }

  if (event.type === HttpEventType.UploadProgress) {
    // {
    // loaded:11, // Number of bytes uploaded or downloaded.
    // total :11 // Total number of bytes to upload or download
    // }
  }

  if (event.type === HttpEventType.Response) {
    console.log(event.body);
  }
})

若是咱們想要跟蹤文件上傳或下載的進度,在建立請求對象時,咱們須要配置 {reportProgress: true} 參數。

此外在回調函數中,咱們經過 event.type 來判斷不一樣的事件類型,從進行相應的事件處理。

HttpEventType 枚舉定義以下:

export enum HttpEventType {
  /**
   * 表示請求已經被髮送
   */
  Sent,

  /**
   * 已接收到上傳進度事件
   */
  UploadProgress,

  /**
   * 已接收到響應狀態碼和響應頭
   */
  ResponseHeader,

  /**
   * 已接收到下載進度事件
   */
  DownloadProgress,

  /**
   * 已接收所有響應,包含響應體 
   */
  Response,

  /**
   * 用戶自定義事件,來自攔截器或後端
   */
  User,
}

其實除了上面介紹三個新的功能以外,還有如下兩個新的功能:

  • 基於 Angular 內部測試框架的 Post-request verificationflush 功能
  • 類型化,同步響應體訪問,包括對 JSON 響應體類型的支持。

最後咱們來經過 client_spec.ts 文件中的測試用例,來進一步感覺一下上述的新特性。

其它特性

發送 GET 請求

describe('HttpClient', () => {
    let client: HttpClient = null !;
    let backend: HttpClientTestingBackend = null !;
    beforeEach(() => {
      backend = new HttpClientTestingBackend();
      client = new HttpClient(backend);
    });
    afterEach(() => { backend.verify(); }); // 請求驗證
  
    describe('makes a basic request', () => {
      it('for JSON data', (done: DoneFn) => {
        client.get('/test').subscribe(res => {
          expect((res as any)['data']).toEqual('hello world');
          done();
        });
        backend.expectOne('/test').flush({'data': 'hello world'});
      });
      
      it('for an arraybuffer', (done: DoneFn) => {
        const body = new ArrayBuffer(4);
        // 還支持 {responseType: 'text'}、{responseType: 'blob'}
        client.get('/test', {responseType: 'arraybuffer'}).subscribe(res => {
          expect(res).toBe(body);
          done();
        });
        backend.expectOne('/test').flush(body);
      });
      
      it('that returns a response', (done: DoneFn) => {
        const body = {'data': 'hello world'};
        client.get('/test', {observe: 'response'}).subscribe(res => {
          expect(res instanceof HttpResponse).toBe(true);
          expect(res.body).toBe(body);
          done();
        });
        backend.expectOne('/test').flush(body);
      });
    });
});

發送 POST 請求

describe('makes a POST request', () => {
      it('with text data', (done: DoneFn) => {
        client.post('/test', 'text body', {observe: 'response', responseType: 'text'})
            .subscribe(res => {
              expect(res.ok).toBeTruthy();
              expect(res.status).toBe(200);
              done();
            });
        backend.expectOne('/test').flush('hello world');
      });
  
      it('with json data', (done: DoneFn) => {
        const body = {data: 'json body'};
        client.post('/test', body, {observe: 'response', 
          responseType: 'text'}).subscribe(res => {
          expect(res.ok).toBeTruthy();
          expect(res.status).toBe(200);
          done();
        });
        const testReq = backend.expectOne('/test');
        expect(testReq.request.body).toBe(body);
        testReq.flush('hello world');
      });
});

發送 JSONP 請求

describe('makes a JSONP request', () => {
      it('with properly set method and callback', (done: DoneFn) => {
        client.jsonp('/test', 'myCallback').subscribe(() => done());
        backend.expectOne({method: 'JSONP', url: '/test?myCallback=JSONP_CALLBACK'})
            .flush('hello world');
      });
});

參考資源

相關文章
相關標籤/搜索