Angular HTTP Client 快速入門

閱讀新版 Angular 6 HttpClient 快速入門

以前 激動人心的 Angular HttpClient 這篇文章已經介紹過 HttpClient ,今天看到 angular-university 博客中介紹 HttpClient 的文章,內容很詳細,我就簡單作了整理。有興趣的話,建議直接閱讀 原文javascript

HttpClientModule 應用

導入新的 HTTP Module

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

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

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

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

如今咱們能夠這樣寫:typescript

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

發送 Get 請求

import {Component, OnInit} from '@angular/core';
import {Observable} from "rxjs/Observable";
import {HttpClient} from "@angular/common/http";
import * as _ from 'lodash';

interface Course {
    description: string;
    courseListIcon:string;
    iconUrl:string;
    longDescription:string;
    url:string;
}

@Component({
  selector: 'app-root',
  template: `
      <ul *ngIf="courses$ | async as courses else noData">
          <li *ngFor="let course of courses">
              {{course.description}}
          </li> 
      </ul>
      <ng-template #noData>No Data Available</ng-template>
  `})
export class AppComponent implements OnInit {
    courses$: Observable<any>;
    constructor(private http:HttpClient) {}

    ngOnInit() {
        this.courses$ = this.http
            .get("https://angular-http-guide.firebaseio.com/courses.json")
            .map(data => _.values(data))
            .do(console.log);
    }
}

設置查詢參數

假設發送 Get 請求時,須要設置對應的查詢參數,預期的 URL 地址以下:shell

https://angular-http-guide.firebaseio.com/courses.json?orderBy="$key"&limitToFirst=1

建立 HttpParams 對象

import {HttpParams} from "@angular/common/http";

const params = new HttpParams()
    .set('orderBy', '"$key"')
    .set('limitToFirst', "1");

this.courses$ = this.http
    .get("/courses.json", {params})
    .do(console.log)
    .map(data => _.values(data))

須要注意的是,咱們經過鏈式語法調用 set() 方法,構建 HttpParams 對象。這是由於 HttpParams 對象是不可變的,經過 set() 方法能夠防止該對象被修改。json

每當調用 set() 方法,將會返回包含新值的 HttpParams 對象,所以若是使用下面的方式,將不能正確的設置參數。bootstrap

const params = new HttpParams();

params.set('orderBy', '"$key"')
params.set('limitToFirst', "1");

使用 fromString 語法

const params = new HttpParams({fromString: 'orderBy="$key"&limitToFirst=1'});

使用 request() API

const params = new HttpParams({fromString: 'orderBy="$key"&limitToFirst=1'});

this.courses$ = this.http
    .request(
        "GET",
        "/courses.json", 
        {
            responseType:"json",
            params
        })
    .do(console.log)
    .map(data => _.values(data));

設置 HTTP Headers

const headers = new HttpHeaders().set("X-CustomHeader", "custom header value");

this.courses$ = this.http
    .get(
        "/courses.json",
        {headers})
    .do(console.log)
    .map(data => _.values(data));

發送 Put 請求

httpPutExample() {
    const headers = new HttpHeaders().set("Content-Type", "application/json");

    this.http.put("/courses/-KgVwECOnlc-LHb_B0cQ.json",
        {
            "courseListIcon": ".../main-page-logo-small-hat.png",
            "description": "Angular Tutorial For Beginners TEST",
            "iconUrl": ".../angular2-for-beginners.jpg",
            "longDescription": "...",
            "url": "new-value-for-url"
        },
        {headers})
        .subscribe(
            val => {
                console.log("PUT call successful value returned in body", 
                  val);
            },
            response => {
                console.log("PUT call in error", response);
            },
            () => {
                console.log("The PUT observable is now completed.");
            }
        );
}

發送 Patch 請求

httpPatchExample() {
    this.http.patch("/courses/-KgVwECOnlc-LHb_B0cQ.json",
        {
            "description": "Angular Tutorial For Beginners PATCH TEST",
        })
        .subscribe(
            (val) => {
                console.log("PATCH call successful value returned in body", 
                  val);
            },
            response => {
                console.log("PATCH call in error", response);
            },
            () => {
                console.log("The PATCH observable is now completed.");
            });
}

發送 Delete 請求

httpDeleteExample() {
    this.http.delete("/courses/-KgVwECOnlc-LHb_B0cQ.json")
        .subscribe(
            (val) => {
                console.log("DELETE call successful value returned in body", 
                  val);
            },
            response => {
                console.log("DELETE call in error", response);
            },
            () => {
                console.log("The DELETE observable is now completed.");
            });
}

發送 Post 請求

httpPostExample() {
    this.http.post("/courses/-KgVwECOnlc-LHb_B0cQ.json",
        {
            "courseListIcon": "...",
            "description": "TEST",
            "iconUrl": "..",
            "longDescription": "...",
            "url": "new-url"
        })
        .subscribe(
            (val) => {
                console.log("POST call successful value returned in body", 
                  val);
            },
            response => {
                console.log("POST call in error", response);
            },
            () => {
                console.log("The POST observable is now completed.");
            });
}

避免重複請求

duplicateRequestsExample() {
    const httpGet$ = this.http
        .get("/courses.json")
        .map(data => _.values(data));

    httpGet$.subscribe(
        (val) => console.log("logging GET value", val)
    );

    this.courses$ = httpGet$;
}

在上面例子中,咱們正在建立了一個 HTTP observable 對象 httpGet$,接着咱們直接訂閱該對象。而後,咱們把 httpGet$ 對象賦值給 courses$ 成員變量,最後在模板中使用 async 管道訂閱該對象。segmentfault

這將致使發送兩個 HTTP 請求,在這種狀況下,請求顯然是重複的,由於咱們只但願從後端查詢一次數據。爲了不發送冗餘的請求,咱們可使用 RxJS 提供的 shareReplay 操做符:後端

// put this next to the other RxJs operator imports
import 'rxjs/add/operator/shareReplay';

const httpGet$ = this.http
    .get("/courses.json")
    .map(data => _.values(data))
    .shareReplay();

並行發送多個請求

並行發送 HTTP 請求的一種方法是使用 RxJs 中的 forkjoin 操做符:api

import 'rxjs/add/observable/forkJoin';

parallelRequests() {

    const parallel$ = Observable.forkJoin(
        this.http.get('/courses/-KgVwEBq5wbFnjj7O8Fp.json'),
        this.http.get('/courses/-KgVwECOnlc-LHb_B0cQ.json')
    );

    parallel$.subscribe(
        values => {
            console.log("all values", values)
        }
    );
}

順序發送 Http 請求

sequentialRequests() {
    const sequence$ = this.http.get<Course>('/courses/-KgVwEBq5wbFnjj7O8Fp.json')
        .switchMap(course => {
            course.description+= ' - TEST ';
            return this.http.put('/courses/-KgVwEBq5wbFnjj7O8Fp.json', course)
        });
        
    sequence$.subscribe();
}

獲取順序發送 Http 請求的結果

sequentialRequests() {
    const sequence$ = this.http.get<Course>('/courses/-KgVwEBq5wbFnjj7O8Fp.json')
        .switchMap(course => {
            course.description+= ' - TEST ';
            return this.http.put('/courses/-KgVwEBq5wbFnjj7O8Fp.json', course)
        },
            (firstHTTPResult, secondHTTPResult)  => [firstHTTPResult, secondHTTPResult]);

    sequence$.subscribe(values => console.log("result observable ", values) );
}

請求異常處理

throwError() {
    this.http
        .get("/api/simulate-error")
        .catch( error => {
            // here we can show an error message to the user,
            // for example via a service
            console.error("error catched", error);

            return Observable.of({description: "Error Value Emitted"});
        })
        .subscribe(
            val => console.log('Value emitted successfully', val),
            error => {
                console.error("This line is never called ",error);
            },
            () => console.log("HTTP Observable completed...")
        );
}

當發生異常時,控制檯的輸出結果:angular2

Error catched 

HttpErrorResponse {headers: HttpHeaders, status: 404, statusText: "Not Found", url: "http://localhost:4200/api/simulate-error", ok: false, … }

Value emitted successfully {description: "Error Value Emitted"}
HTTP Observable completed...

Http 攔截器

定義攔截器

import {Injectable} from "@angular/core";
import {HttpEvent, HttpHandler, HttpInterceptor} from "@angular/common/http";
import {HttpRequest} from "@angular/common/http";
import {Observable} from "rxjs/Observable";

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
    
    constructor(private authService: AuthService) {
    }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        const clonedRequest = req.clone({
            headers: req.headers.set('X-CustomAuthHeader', authService.getToken())
        });
        console.log("new headers", clonedRequest.headers.keys());
        return next.handle(clonedRequest);
    }
}

配置攔截器

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        HttpClientModule
    ],
    providers: [
        [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true } ]
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

Http 進度事件

longRequest() {
    const request = new HttpRequest(
        "POST", "/api/test-request", {}, 
         {reportProgress: true});

    this.http.request(request)
        .subscribe(
            event => {
                if (event.type === HttpEventType.DownloadProgress) {
                    console.log("Download progress event", event);
                }
                if (event.type === HttpEventType.UploadProgress) {
                    console.log("Upload progress event", event);
                }
                if (event.type === HttpEventType.Response) {
                    console.log("response received...", event.body);
                }
            }
        );
}

上面示例運行後,控制檯的可能的輸出結果:

Upload progress event Object {type: 1, loaded: 2, total: 2}
Download progress event Object {type: 3, loaded: 31, total: 31}
Response Received... Object {description: "POST Response"}