Angular 4+ Http

HTTP: 使應用可以對遠端服務器發起相應的Http調用;css

你要知道:html

 HttpModule並非Angular的核心模塊,它是Angualr用來進行Web訪問的一種可選方式,並位於一個名叫@angual/http的獨立附屬模塊中;也就是說:使用http以前要引入此模塊;node

1.基本使用:程序員

    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import {HttpModule} from '@angular/http';  /*引入它*/
    import { AppComponent } from './app.component';
    @NgModule({
      declarations: [
        AppComponent
      ],
      imports: [
        BrowserModule,
        HttpModule  /*導入*/
      ],
      providers: [],
      bootstrap: [AppComponent]
    })
    export class AppModule { }

注意,如今HttpModule已是根模塊AppModuleimports數組的一部分了;web

2.介紹模擬web api 發送請求數據庫

注意:是在你沒有真實的遠程服務請求地址時,本身模擬一個web服務,用於測試代碼;假若有真實的遠程服務器,則不須要這個;npm

  2.1 引入InMemoryWebApiModule,這是Angular提供的一個輔助服務,負責與遠程服務器對話 — 替換成了內存 Web API服務,相似於後端服務; json

import { InMemoryWebApiModule } from 'angular-in-memory-web-api';

注意:導入這個,若是出現錯誤,多是你的node-module 文件庫裏面沒有此庫,這時候打開你的package.json文件:bootstrap

 "dependencies"

裏面配置這樣一句:後端

"angular-in-memory-web-api": "^0.3.2"

從新運行npm install ;

  2.2 向內存服務中注入數據,若是沒有數據是不能運行的;

import { InMemoryDataService }  from './in-memory-data.service';

注意幾點:

    in-memory-data.service.ts 文件是你本身建立的,用來存放實例數據的;
    in-memory-data.service.ts 這樣寫:
    import { InMemoryDbService } from 'angular-in-memory-web-api';
    export class InMemoryDataService  implements InMemoryDbService{
       createDb() {
        const heroes = [
          { id: 0,  name: 'Zero' },
          { id: 1, name: 'Mr. Nice' },
          { id: 2, name: 'Narco' },
          { id: 3, name: 'Bombasto' },
        ];
        return {heroes};
      }
    }

3.這裏至關於本身建立了一個內部數據庫;

  2.3 導入的文件如何在app.module.ts中注入:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import {HttpModule} from '@angular/http';
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService }  from './in-memory-data.service';
import { AppComponent } from './app.component';
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpModule,
    InMemoryWebApiModule.forRoot(InMemoryDataService),  /*這樣注入*/
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

這裏如今就是所有代碼;

3.如何發起http請求;

  3.1建立一個服務;

import {Injectable} from '@angular/core';
@Injectable()
export class HeroService{
}

angular 中全部建立服務的模式都是這樣,咱們建立了一個HeroService的文件,下載要在app.module.ts中注入它;

import {HeroService} from './hero.service';
  providers: [HeroService],
  bootstrap: [AppComponent]

注意:

1.providers 是專門用來注入服務的;
2. 能夠在app.module.ts裏的providers中注入服務,也能夠在你的子模塊和組件中注入,
前者是共享的服務,一次注入,整個項目都有效,若是在子模塊中或者子組件中注入,這只是你屬於當前模塊或當前組件下有效,其餘的組件須要使用,都得本身從新注入;
通常狀況下:返回的是一個能解析的promise(承諾)

 3.2  引入http  ;

import {Http} from '@angular/http';
constructor(private http:Http){ }

必定要在構造裏建立它;

3.3 嘗試獲取內存數據庫裏面的信息;

 import {Injectable} from '@angular/core';
import {Http} from '@angular/http';
import 'rxjs/add/operator/toPromise';
import {Hero} from './hero';
@Injectable()
export class HeroService{
    constructor(private http:Http){ 
/*註釋1*/ private heroUrl='api/heroes';
/*註釋2*/ getHeroes(): Promise
<Hero[]>{ return this.http.get(this.heroUrl) /*註釋3*/ .toPromise() /*註釋4*/ .then(response =>response.json().data as Hero[]) /*註釋5*/ .catch((error: any) => Promise.reject(error || 'Server error')); } }

如今我寫了一個getHeroes()的方法:

解析幾點:

 註釋1:http請求確定須要Url地址,這裏須要導入數據庫裏面的名;api/heroes 其實就是你內存數據庫 web api裏面
return {heroes};

根據你返回的值,調用相應的api地址;

 註釋2: 使用的是http承諾(promise),必定要有返回值,返回的是一個能夠解析成英雄列表的Observable對象 ,也就是咱們的Hero;也就是須要導入這個
import {Hero} from './hero';

hero.ts文件包含:

export class Hero{
    id:number;
    name:string;
}

Observable(可觀察對象)是一個管理異步數據流的強力方式,後續會學習;

註釋3: .toPromise會將咱們獲得的Observable對象轉化成promise對象;

  可是Angular中的observable對象並無一個topromise操做符,這裏就須要藉助其餘工具,

import 'rxjs/add/operator/toPromise';

更多詳細的能夠看rxjs庫操做符;

註釋4:在 then 回調中提取出數據,這也是angular中一大特色,默認 JSON 解析,這個由json方法返回的對象只有一個data屬性。 
這個data屬性保存了英雄數組,這個數組纔是調用者真正想要的。 因此咱們取得這個數組,而且把它做爲承諾的值進行解析

注意:web api 返回的是帶有data屬性的對象,而你真是的api能夠返回其餘值的;

 註釋5:錯誤處理,有異常很正常,咱們能夠將他們傳給錯誤處理器;如今咱們只是將錯誤記錄到控制檯,真實案例中,能夠將錯誤進行處理;

4. 如何將得到的數據顯示在界面;

  4.1 組件中調用Hero.service.ts;

import { Component } from '@angular/core';
import {HeroService} from './hero.service';
import {Hero}  from './hero';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
 /*註釋1*/ hero:Hero[];
 /*註釋2*/ constructor(private service:HeroService){
             this.getHeroes();
            }
          getHeroes(){
/*註釋3*/     this.service.getHeroes().then(
               hero=>{
                 this.hero=hero;
                  console.log(this.hero);
                    }
/*註釋4*/     ).catch((error: any) => Promise.reject(error || 'client error'))
           }
}
註釋1: 用來接收返回的對象;
註釋2: 對引入的hero.service.ts服務初始化;
註釋3: 返回的是promise對象,用then方法去解析獲得的值;
註釋4: 預期會犯錯,拋出異常;

如今能夠看看控制檯打印的hero對象:

 

如何顯示在咱們的html上呢?

 <ul *ngFor="let a of hero">
   <li>{{a.id}} : {{a.name}}</li>
 </ul>

簡單的寫了這樣一段:

這裏注意:多對數組或對象形式用*ngFor遍歷;

運行localhost:4200,這樣就可讓數據正常顯示啦;

 這裏介紹的是採用promise的形式調用http的方法,原理就是:hero.service中,http.get()返回的Observable後面串聯了一個toPromise操做符。 該操做符把Observable轉換成了Promise,而且咱們把那個承諾返回給了調用者;

5. http promise還能作什麼?

  5.1 嘗試經過id獲取英雄;

  hero.service.ts中添加這段代碼:

 getHero(id:number):Promise<Hero>{
         const url = `${this.heroUrl}/${id}`;
         return this.http.get(url)
         .toPromise()
         .then(response =>
                 response.json().data as Hero,  
          )
         .catch((error: any) => Promise.reject(error || 'Server error'));
     }

和獲取全部英雄的方法相似,這個的步驟是先獲取全部的英雄,並從中過濾出與id匹配的那一個;

匹配api/hero/:id 模式,有點像路由;

  5.2 組件中如何調用;

  detailhero:Hero;

getHero(id:number){
    this.service.getHero(id).then(
       hero=>{
         this.detailhero=hero;
         console.log(this.detailhero);
        }
   ).catch((error: any) => Promise.reject(error || 'client error'))
  }

方法都相似,只是你須要傳入想查詢的id;

constructor中運行此方法,好比我想知道第二個英雄的詳情:

this.getHero(2);

  看看控制檯輸出:

 

  看看html代碼:

 <p *ngIf="detailhero">{{detailhero.name}}</p>

注意:單個對象用*ngIf ;

6. http promise還有什麼請求方式?

request(url: string | Request, options?: RequestOptionsArgs): Observable<Response>;
    /**
     * Performs a request with `get` http method.
     */
    get(url: string, options?: RequestOptionsArgs): Observable<Response>;
    /**
     * Performs a request with `post` http method
     */
    post(url: string, body: any, options?: RequestOptionsArgs): Observable<Response>;
    /**
     * Performs a request with `put` http method.
     */
    put(url: string, body: any, options?: RequestOptionsArgs): Observable<Response>;
    /**
     * Performs a request with `delete` http method.
     */
    delete(url: string, options?: RequestOptionsArgs): Observable<Response>;
    /**
     * Performs a request with `patch` http method.
     */
    patch(url: string, body: any, options?: RequestOptionsArgs): Observable<Response>;
    /**
     * Performs a request with `head` http method.
     */
    head(url: string, options?: RequestOptionsArgs): Observable<Response>;
    /**
     * Performs a request with `options` http method.
     */
    options(url: string, options?: RequestOptionsArgs): Observable<Response>;

 6.1 咱們來添加一個英雄;

   hero.service.ts中加入這一段:

/*註釋1*/ private headers = new Headers({'Content-Type': 'application/json'});
      addHero(name:string):Promise<Hero>{
          return this.http.post(this.heroUrl,JSON.stringify({name:name}),{headers:this.headers})
          .toPromise()
          .then(
              res=>
                  res.json().data as Hero
          )
          .catch((error: any) => Promise.reject(error || 'Server error'));
      }

註釋1:

import {Http,Headers} from '@angular/http';

須要引入Headers,而後new 一個實例;

可是這個參數是可選的;

組件中怎麼接收:

addhero:Hero;
  addHero(name:string){
    this.service.addHero(name).then(
        hero=>{
          this.addhero=hero;
          console.log(this.addhero);
          this.hero.push(this.addhero);  /*將添加的數據Push進hero數組*/
        }
    ).catch((error: any) => Promise.reject(error || 'client error'))
  }

同樣的代碼,就不過多解釋;

咱們的英雄在哪裏添加呢?

  確定是要有一個input輸入框的:

 <div>
     add:<input #heroName/>
     <button (click)="addHero(heroName.value);heroName.value=''">add</button>
 </div>

添加以後,能夠看到控制檯會有輸出:

 

可是界面顯示的仍是這些:

 

Angular會自動檢測數據的變換,當有數據變換時,會實時更新

還有一些其餘方法,本身去嘗試下;

hero.service.ts:

import { Injectable } from '@angular/core';
import { Http, Headers,Response} from '@angular/http';
import 'rxjs/add/operator/toPromise';
import { Hero } from './hero';
@Injectable()
export class HeroService {
    constructor(private http: Http) { }
    private heroUrl = 'api/heroes';
    getHeroes(): Promise<Hero[]> {
        return this.http.get(this.heroUrl)
            .toPromise()
            .then( this.extractData )  /*註釋1*/
            .catch((error: any) => Promise.reject(error || 'Server error'));/*註釋2*/
    }

    getHero(id: number): Promise<Hero> {
        const url = `${this.heroUrl}/${id}`;
        return this.http.get(url)
            .toPromise()
            .then(this.extractData)
            .catch(this.handleError);
    }
    private headers = new Headers({ 'Content-Type': 'application/json' });
    addHero(name: string): Promise<Hero> {
        return this.http.post(this.heroUrl, JSON.stringify({ name: name }), { headers: this.headers })
            .toPromise()
            .then(this.extractData)
            .catch(this.handleError);
    }

   /**
    *
    * @param res 接收數據的方法
    */
    private extractData(res: Response)  {  /*註釋2*/
        let body = res.json();
        return body.data|| {}; /*註釋3*/
    }

    /**
     * 
     * @param error 處理錯誤的方法
     */
    private handleError(error: Response | any) {
        return Promise.reject(error || 'Server error');
    }
}
註釋1:將then()和catch()裏面的部分提取出來,每次使用只須要調用方法便可;
註釋2:接收的Response記得將Response在Http對象中引入;
註釋3:這裏接收的data實際上是一個any型;

7. Observable對象

 Http服務中的每一個方法都返回一個 HTTP Response對象的Observable實例;

Observable 就是一個擁有如下特性的函數:

  • 它接收一個 observer 對象做爲參數,該對象中包含 nexterrorcomplete 方法
  • 它返回一個函數,用於在銷燬 Observable 時,執行清理操做

在 RxJS 中,返回的是 Subcription 對象,該對象中包含一個 unsubscribe 方法。

一個 Observable 對象設置觀察者 (observer),並將它與生產者關聯起來。該生產者多是 DOM 元素產生的 clickinput 事件,也多是更復雜的事件,如 HTTP。

當 Observable 對象產生新值的時候,咱們能夠經過調用 next() 方法來通知對應的觀察者。若出現異常,則會調用觀察者的 error() 方法。當咱們訂閱 Observable 對象後,只要有新的值,都會通知對應的觀察者。但在如下兩種狀況下,新的值不會再通知對應的觀察者:

  • 已調用 observer 對象的 complete() 方法
  • 消費者對數據再也不感興趣,執行取消訂閱操做

此外在執行最終的 subscribe() 訂閱操做前,咱們傳遞的值能夠通過一系列的鏈式處理操做。執行對應操做的東西叫操做符,每一個操做符執行完後會返回一個新的 Observable 對象,而後繼續咱們的處理流程。

那麼咱們能夠直接返回Observable給組件嗎?

咱們能夠看看,以前getHero()那一段若是直接改爲返回Observable對象會如何;

getHeroes():Observable<Hero[]>{  /*註釋1*/
        return this.http.get(this.heroUrl)
        .map(this.extractData); 
    }
註釋1:不用調用toPromise()方法,天然也不用then()去取數據,而是直接返回Observable對象以後

調用RXJS中的map操做符來從返回的數據中提取數據;

因此你要引入:

import { Observable }  from 'rxjs/Observable';
import 'rxjs/add/operator/map';

操做符以後會講到,注意和以前的promise方法對比;

組件中怎麼接收數據Observable對象呢?

 getHeroes(){
    this.service.getHeroes().subscribe(    /*註釋1*/
      hero=>{
        this.hero=hero;
        console.log(this.hero);
      }
    )
  }

這樣顯示的效果也是同樣的;

註釋1:調用 subscribe() 方法,來獲取最終的值,採起訂閱的方式,而不是用then();

8. Observabel和Promise 有什麼不一樣

   8.1 兩個均可以用於web API http的調用,但Observable是能夠中途取消的,而Promise一旦觸發就不能被取消;

 setTimeout(() => {
        this.service.getHero().unsubscribe();
      }, 1000);

   8.2  由於Promise只能發起一次請求,只要接收到數據,就會算完成,因此Promise只能發射一個值就算結束,Observable能夠連續發送好幾個值,在服務器對第一個請求做出迴應以前,再開始另外一個不一樣的請求;

this.heroes = this.searchTerms
    .debounceTime(300)        
    .distinctUntilChanged()  
    .switchMap(term => term   
      ? this.service.search(term)
      : Observable.of<Hero[]>([]))
    .catch(error => {
      console.log(error);
      return Observable.of<Hero[]>([]);
    });
this.heroes.subscribe(value => console.log(value));

這個後面代碼操做符這塊有詳解;

 8.3 Observable提供了不少的工具函數,最最最經常使用的filter和map;詳細看看這兩個map和filter;

 map() 操做符返回一個新的 Observable 對象
 filter() 操做符執行過濾操做,而後又返回一個新的 Observable 對象

最後咱們經過調用 subscribe() 方法,來獲取最終的值

Filter:過濾器

getHeroes(){
    this.service.getHeroes().filter(hero => hero.length >= 2)
    .subscribe(
        hero=>{
           this.hero=hero;
        }
    )
  }

Map的用法上面有介紹;

可是,使用承諾的方式能讓調用方法更容易寫,而且承諾已經在 JavaScript 程序員中被普遍接受了。

  再來看看Observable操做符:

   根據名字搜索用戶:

search(term: string): Observable<Hero[]> {
        return this.http
               .get(`api/heroes/?name=${term}`)
               .map(response => response.json().data as Hero[]);
    }

 看看組件代碼:

 import { Observable } from 'rxjs/Observable';  /*註釋1*/
import { Subject }  from 'rxjs/Subject';  /*註釋2*/
// Observable class extensions
import 'rxjs/add/observable/of';  /*註釋3*/
// Observable operators
import 'rxjs/add/operator/catch';  /*註釋4*/
import 'rxjs/add/operator/debounceTime';  /*註釋5*/
import 'rxjs/add/operator/distinctUntilChanged';  /*註釋6*/
import 'rxjs/add/operator/switchMap'; /*註釋6*/

引入這些操做符:

註釋1:引入RXJS的可觀察對象;
註釋2:Suject是一個可觀察事件流中的生產者,經過調用Next()對象,能夠將新觀察的對象放入可觀察流中;
註釋3:是Rxjs爲Observable對象提供的擴展,
註釋4:異常操做符,攔截失敗的可觀察對象。
註釋5:至關於settimeout(),延遲;
註釋6:distinctUntilChanged確保只在過濾條件變化時才發送請求, 這樣就不會重複請求同一個搜索詞了;
註釋7:switchMap()會爲每一個從debounce和distinctUntilChanged中經過的搜索詞調用搜索服務。 它會取消並丟棄之前的搜索可觀察對象,只保留最近的;使用這個每觸發一次事件都會引發一次Http()的調用,即便你使用延遲,但同一時間仍是會有多個Http請求,而且返回的順序不必定是請求發起的順序;

 看看Http()方法調用:

 heroes: Observable<Hero[]>;
  private searchTerms=new Subject<string>();   // searchTerms生成一個產生字符串的Observable
  /*每當調用search()時都會調用next()來把新的字符串放進該主題的可觀察流中*/
  search(term:string):void{
    this.searchTerms.next(term);
  }
   ngOnInit(): void {
     this.heroes = this.searchTerms
    .debounceTime(300)        
    .distinctUntilChanged()  
    .switchMap(term => term   
      ? this.service.search(term)
      : Observable.of<Hero[]>([]))
    .catch(error => {
      console.log(error);
      return Observable.of<Hero[]>([]);
    });
}

看看代碼:

 <div>
  search :<input #searchBox (keyup)="search(searchBox.value)"/>
     <div  *ngFor="let b of heroes | async">
        {{b.name}}
    </div>
</div>

爲何使用一個async管道呢?由於獲得的是Observable對象,不是數組,*ngFor不能遍歷可觀察對象;

效果:

 

看完這個,大概知道:

  Observable(可觀察對象)是基於推送(Push)運行時執行(lazy)的多值集合;

關於操做符,我有話要說:

   大部分RxJS操做符都不包括在Angular的Observable基本實現中,基本實現只包括Angular自己所需的功能,也就是,你須要的大多操做符都須要從rxjs中引入;

 

9. 上述的例子,其實說的都是關於web api,內存服務器的,佔用內存的東西,速率都有點慢,若是你要從本地獲取文件記住不要用這個;

能夠換用json文件獲取:

 heroes.json:
{
  "data": [
    { "id": 1, "name": "Windstorm" },
    { "id": 2, "name": "Bombasto" },
    { "id": 3, "name": "Magneta" },
    { "id": 4, "name": "Tornado" }
  ]
 建立一個heroes.json,記得放在assets文件下: 
{
  "data": [
    { "id": 1, "name": "Windstorm" },
    { "id": 2, "name": "Bombasto" },
    { "id": 3, "name": "Magneta" },
    { "id": 4, "name": "Tornado" }
  ]
}

 

咱們app.module.ts裏面的:

   InMemoryWebApiModule.forRoot(InMemoryDataService),

不用引入,引入以後會默認使用web api;

而後hero.service.ts中這樣

private heroesUrl = 'assets/heroes.json';

其餘的都是同樣的;

這種方式對於請求靜態的本地數據是比較有用的;若是是請求真是的遠程服務器地址,url換成遠程地址就能夠;

相關文章
相關標籤/搜索