Angular 4+ Http
HTTP: 使應(yīng)用能夠?qū)h(yuǎn)端服務(wù)器發(fā)起相應(yīng)的Http調(diào)用;
你要知道:
HttpModule并不是Angular的核心模塊,它是Angualr用來(lái)進(jìn)行Web訪問(wèn)的一種可選方式,并位于一個(gè)名叫@angual/http的獨(dú)立附屬模塊中;也就是說(shuō):使用http之前要引入此模塊;
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 /*導(dǎo)入*/
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
注意,現(xiàn)在HttpModule已經(jīng)是根模塊AppModule的imports數(shù)組的一部分了;
2.介紹模擬web api 發(fā)送請(qǐng)求
注意:是在你沒(méi)有真實(shí)的遠(yuǎn)程服務(wù)請(qǐng)求地址時(shí),自己模擬一個(gè)web服務(wù),用于測(cè)試代碼;倘若有真實(shí)的遠(yuǎn)程服務(wù)器,則不需要這個(gè);
2.1 引入InMemoryWebApiModule,這是Angular提供的一個(gè)輔助服務(wù),負(fù)責(zé)與遠(yuǎn)程服務(wù)器對(duì)話 — 替換成了內(nèi)存 Web API服務(wù),類似于后端服務(wù);
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
注意:導(dǎo)入這個(gè),如果出現(xiàn)錯(cuò)誤,可能是你的node-module 文件庫(kù)里面沒(méi)有此庫(kù),這時(shí)候打開(kāi)你的package.json文件:
"dependencies"
里面配置這樣一句:
"angular-in-memory-web-api": "^0.3.2"
重新運(yùn)行npm install ;
2.2 向內(nèi)存服務(wù)中注入數(shù)據(jù),如果沒(méi)有數(shù)據(jù)是不能運(yùn)行的;
import { InMemoryDataService } from './in-memory-data.service';
注意幾點(diǎn):
in-memory-data.service.ts 文件是你自己創(chuàng)建的,用來(lái)存放實(shí)例數(shù)據(jù)的;
in-memory-data.service.ts 這樣寫(xiě):
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.這里相當(dāng)于自己創(chuàng)建了一個(gè)內(nèi)部數(shù)據(jù)庫(kù);
2.3 導(dǎo)入的文件如何在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 { }
這里現(xiàn)在就是全部代碼;
3.如何發(fā)起http請(qǐng)求;
3.1創(chuàng)建一個(gè)服務(wù);
import {Injectable} from '@angular/core';
@Injectable()
export class HeroService{
}
angular 中所有創(chuàng)建服務(wù)的模式都是這樣,我們創(chuàng)建了一個(gè)HeroService的文件,下載要在app.module.ts中注入它;
import {HeroService} from './hero.service';
providers: [HeroService],
bootstrap: [AppComponent]
注意:
1.providers 是專門用來(lái)注入服務(wù)的;
2. 可以在app.module.ts里的providers中注入服務(wù),也可以在你的子模塊和組件中注入,
前者是共享的服務(wù),一次注入,整個(gè)項(xiàng)目都有效,如果在子模塊中或者子組件中注入,這只是你屬于當(dāng)前模塊或當(dāng)前組件下有效,其他的組件需要使用,都得自己重新注入;
一般情況下:返回的是一個(gè)能解析的promise(承諾)
3.2 引入http ;
import {Http} from '@angular/http';
constructor(private http:Http){ }
一定要在構(gòu)造里創(chuàng)建它;
3.3 嘗試獲取內(nèi)存數(shù)據(jù)庫(kù)里面的信息;
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')); } }
現(xiàn)在我寫(xiě)了一個(gè)getHeroes()的方法:
解析幾點(diǎn):
注釋1:http請(qǐng)求肯定需要Url地址,這里需要導(dǎo)入數(shù)據(jù)庫(kù)里面的名;api/heroes 其實(shí)就是你內(nèi)存數(shù)據(jù)庫(kù) web api里面
return {heroes};
根據(jù)你返回的值,調(diào)用相應(yīng)的api地址;
注釋2: 使用的是http承諾(promise),一定要有返回值,返回的是一個(gè)可以解析成英雄列表的Observable對(duì)象 ,也就是我們的Hero;也就是需要導(dǎo)入這個(gè)
import {Hero} from './hero';
hero.ts文件包含:
export class Hero{
id:number;
name:string;
}
Observable(可觀察對(duì)象)是一個(gè)管理異步數(shù)據(jù)流的強(qiáng)力方式,后續(xù)會(huì)學(xué)習(xí);
注釋3: .toPromise會(huì)將我們得到的Observable對(duì)象轉(zhuǎn)化成promise對(duì)象;
但是Angular中的observable對(duì)象并沒(méi)有一個(gè)topromise操作符,這里就需要借助其他工具,
import 'rxjs/add/operator/toPromise';
更多詳細(xì)的可以看rxjs庫(kù)操作符;
注釋4:在 then 回調(diào)中提取出數(shù)據(jù),這也是angular中一大特點(diǎn),默認(rèn) JSON 解析,這個(gè)由json方法返回的對(duì)象只有一個(gè)data屬性。
這個(gè)data屬性保存了英雄數(shù)組,這個(gè)數(shù)組才是調(diào)用者真正想要的。 所以我們?nèi)〉眠@個(gè)數(shù)組,并且把它作為承諾的值進(jìn)行解析
注意:web api 返回的是帶有data屬性的對(duì)象,而你真是的api可以返回其他值的;
注釋5:錯(cuò)誤處理,有異常很正常,我們可以將他們傳給錯(cuò)誤處理器;現(xiàn)在我們只是將錯(cuò)誤記錄到控制臺(tái),真實(shí)案例中,可以將錯(cuò)誤進(jìn)行處理;
4. 如何將獲得的數(shù)據(jù)顯示在界面;
4.1 組件中調(diào)用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: 用來(lái)接收返回的對(duì)象; 注釋2: 對(duì)引入的hero.service.ts服務(wù)初始化; 注釋3: 返回的是promise對(duì)象,用then方法去解析得到的值; 注釋4: 預(yù)期會(huì)犯錯(cuò),拋出異常;
現(xiàn)在可以看看控制臺(tái)打印的hero對(duì)象:

如何顯示在我們的html上呢?
<ul *ngFor="let a of hero"> <li>{{a.id}} : {{a.name}}</li> </ul>
簡(jiǎn)單的寫(xiě)了這樣一段:
這里注意:多對(duì)數(shù)組或?qū)ο笮问接?ngFor遍歷;
運(yùn)行l(wèi)ocalhost:4200,這樣就可以讓數(shù)據(jù)正常顯示啦;

這里介紹的是采用promise的形式調(diào)用http的方法,原理就是:hero.service中,http.get()返回的Observable后面串聯(lián)了一個(gè)toPromise操作符。 該操作符把Observable轉(zhuǎn)換成了Promise,并且我們把那個(gè)承諾返回給了調(diào)用者;
5. http promise還能做什么?
5.1 嘗試通過(guò)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')); }
和獲取所有英雄的方法類似,這個(gè)的步驟是先獲取所有的英雄,并從中過(guò)濾出與id匹配的那一個(gè);
匹配api/hero/:id 模式,有點(diǎn)像路由;
5.2 組件中如何調(diào)用;
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中運(yùn)行此方法,比如我想知道第二個(gè)英雄的詳情:
this.getHero(2);
看看控制臺(tái)輸出:

看看html代碼:
<p *ngIf="detailhero">{{detailhero.name}}</p>
注意:?jiǎn)蝹€(gè)對(duì)象用*ngIf ;

6. http promise還有什么請(qǐng)求方式?
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 我們來(lái)添加一個(gè)英雄;
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 一個(gè)實(shí)例;
但是這個(gè)參數(shù)是可選的;
組件中怎么接收:
addhero:Hero;
addHero(name:string){
this.service.addHero(name).then(
hero=>{
this.addhero=hero;
console.log(this.addhero);
this.hero.push(this.addhero); /*將添加的數(shù)據(jù)Push進(jìn)hero數(shù)組*/
}
).catch((error: any) => Promise.reject(error || 'client error'))
}
一樣的代碼,就不過(guò)多解釋;
我們的英雄在哪里添加呢?
肯定是要有一個(gè)input輸入框的:
<div> add:<input #heroName/> <button (click)="addHero(heroName.value);heroName.value=''">add</button> </div>
添加之后,可以看到控制臺(tái)會(huì)有輸出:

但是界面顯示的還是這些:

Angular會(huì)自動(dòng)檢測(cè)數(shù)據(jù)的變換,當(dāng)有數(shù)據(jù)變換時(shí),會(huì)實(shí)時(shí)更新
還有一些其他方法,自己去嘗試下;
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 接收數(shù)據(jù)的方法 */ private extractData(res: Response) { /*注釋2*/ let body = res.json(); return body.data|| {}; /*注釋3*/ } /** * * @param error 處理錯(cuò)誤的方法 */ private handleError(error: Response | any) { return Promise.reject(error || 'Server error'); } }
注釋1:將then()和catch()里面的部分提取出來(lái),每次使用只需要調(diào)用方法即可; 注釋2:接收的Response記得將Response在Http對(duì)象中引入; 注釋3:這里接收的data其實(shí)是一個(gè)any型;
7. Observable對(duì)象
Http服務(wù)中的每個(gè)方法都返回一個(gè) HTTP Response對(duì)象的Observable實(shí)例;
Observable 就是一個(gè)擁有以下特性的函數(shù):
- 它接收一個(gè)
observer對(duì)象作為參數(shù),該對(duì)象中包含next、error和complete方法 - 它返回一個(gè)函數(shù),用于在銷毀 Observable 時(shí),執(zhí)行清理操作
在 RxJS 中,返回的是 Subcription 對(duì)象,該對(duì)象中包含一個(gè) unsubscribe 方法。
一個(gè) Observable 對(duì)象設(shè)置觀察者 (observer),并將它與生產(chǎn)者關(guān)聯(lián)起來(lái)。該生產(chǎn)者可能是 DOM 元素產(chǎn)生的 click 或 input 事件,也可能是更復(fù)雜的事件,如 HTTP。
當(dāng) Observable 對(duì)象產(chǎn)生新值的時(shí)候,我們可以通過(guò)調(diào)用 next() 方法來(lái)通知對(duì)應(yīng)的觀察者。若出現(xiàn)異常,則會(huì)調(diào)用觀察者的 error() 方法。當(dāng)我們訂閱 Observable 對(duì)象后,只要有新的值,都會(huì)通知對(duì)應(yīng)的觀察者。但在以下兩種情況下,新的值不會(huì)再通知對(duì)應(yīng)的觀察者:
- 已調(diào)用 observer 對(duì)象的
complete()方法 - 消費(fèi)者對(duì)數(shù)據(jù)不再感興趣,執(zhí)行取消訂閱操作
此外在執(zhí)行最終的 subscribe() 訂閱操作前,我們傳遞的值可以經(jīng)過(guò)一系列的鏈?zhǔn)教幚聿僮鳌?zhí)行對(duì)應(yīng)操作的東西叫操作符,每個(gè)操作符執(zhí)行完后會(huì)返回一個(gè)新的 Observable 對(duì)象,然后繼續(xù)我們的處理流程。
那么我們可以直接返回Observable給組件嗎?
我們可以看看,之前getHero()那一段如果直接改成返回Observable對(duì)象會(huì)如何;
getHeroes():Observable<Hero[]>{ /*注釋1*/ return this.http.get(this.heroUrl) .map(this.extractData); }
注釋1:不用調(diào)用toPromise()方法,自然也不用then()去取數(shù)據(jù),而是直接返回Observable對(duì)象之后
調(diào)用RXJS中的map操作符來(lái)從返回的數(shù)據(jù)中提取數(shù)據(jù);
所以你要引入:
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
操作符之后會(huì)講到,注意和之前的promise方法對(duì)比;
組件中怎么接收數(shù)據(jù)Observable對(duì)象呢?
getHeroes(){
this.service.getHeroes().subscribe( /*注釋1*/
hero=>{
this.hero=hero;
console.log(this.hero);
}
)
}
這樣顯示的效果也是一樣的;
注釋1:調(diào)用 subscribe() 方法,來(lái)獲取最終的值,采取訂閱的方式,而不是用then();
8. Observabel和Promise 有什么不同
8.1 兩個(gè)都可以用于web API http的調(diào)用,但Observable是可以中途取消的,而Promise一旦觸發(fā)就不能被取消;
setTimeout(() => {
this.service.getHero().unsubscribe();
}, 1000);
8.2 因?yàn)镻romise只能發(fā)起一次請(qǐng)求,只要接收到數(shù)據(jù),就會(huì)算完成,所以Promise只能發(fā)射一個(gè)值就算結(jié)束,Observable可以連續(xù)發(fā)送好幾個(gè)值,在服務(wù)器對(duì)第一個(gè)請(qǐng)求作出回應(yīng)之前,再開(kāi)始另一個(gè)不同的請(qǐng)求;
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));
這個(gè)后面代碼操作符這塊有詳解;
8.3 Observable提供了很多的工具函數(shù),最最最常用的filter和map;詳細(xì)看看這兩個(gè)map和filter;
map() 操作符返回一個(gè)新的 Observable 對(duì)象
filter() 操作符執(zhí)行過(guò)濾操作,然后又返回一個(gè)新的 Observable 對(duì)象
最后我們通過(guò)調(diào)用 subscribe() 方法,來(lái)獲取最終的值
Filter:過(guò)濾器
getHeroes(){
this.service.getHeroes().filter(hero => hero.length >= 2)
.subscribe(
hero=>{
this.hero=hero;
}
)
}
Map的用法上面有介紹;
但是,使用承諾的方式能讓調(diào)用方法更容易寫(xiě),并且承諾已經(jīng)在 JavaScript 程序員中被廣泛接受了。
再來(lái)看看Observable操作符:
根據(jù)名字搜索用戶:
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的可觀察對(duì)象; 注釋2:Suject是一個(gè)可觀察事件流中的生產(chǎn)者,通過(guò)調(diào)用Next()對(duì)象,可以將新觀察的對(duì)象放入可觀察流中; 注釋3:是Rxjs為Observable對(duì)象提供的擴(kuò)展, 注釋4:異常操作符,攔截失敗的可觀察對(duì)象。 注釋5:相當(dāng)于settimeout(),延遲; 注釋6:distinctUntilChanged確保只在過(guò)濾條件變化時(shí)才發(fā)送請(qǐng)求, 這樣就不會(huì)重復(fù)請(qǐng)求同一個(gè)搜索詞了; 注釋7:switchMap()會(huì)為每個(gè)從debounce和distinctUntilChanged中通過(guò)的搜索詞調(diào)用搜索服務(wù)。 它會(huì)取消并丟棄以前的搜索可觀察對(duì)象,只保留最近的;使用這個(gè)每觸發(fā)一次事件都會(huì)引起一次Http()的調(diào)用,即使你使用延遲,但同一時(shí)間還是會(huì)有多個(gè)Http請(qǐng)求,并且返回的順序不一定是請(qǐng)求發(fā)起的順序;
看看Http()方法調(diào)用:
heroes: Observable<Hero[]>; private searchTerms=new Subject<string>(); // searchTerms生成一個(gè)產(chǎn)生字符串的Observable /*每當(dāng)調(diào)用search()時(shí)都會(huì)調(diào)用next()來(lái)把新的字符串放進(jìn)該主題的可觀察流中*/ 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>
為什么使用一個(gè)async管道呢?因?yàn)榈玫降氖荗bservable對(duì)象,不是數(shù)組,*ngFor不能遍歷可觀察對(duì)象;
效果:

看完這個(gè),大概知道:
Observable(可觀察對(duì)象)是基于推送(Push)運(yùn)行時(shí)執(zhí)行(lazy)的多值集合;
關(guān)于操作符,我有話要說(shuō):
大部分RxJS操作符都不包括在Angular的Observable基本實(shí)現(xiàn)中,基本實(shí)現(xiàn)只包括Angular本身所需的功能,也就是,你需要的大多操作符都需要從rxjs中引入;
9. 上述的例子,其實(shí)說(shuō)的都是關(guān)于web api,內(nèi)存服務(wù)器的,占用內(nèi)存的東西,速率都有點(diǎn)慢,如果你要從本地獲取文件記住不要用這個(gè);
可以換用json文件獲取:
heroes.json:
{
"data": [
{ "id": 1, "name": "Windstorm" },
{ "id": 2, "name": "Bombasto" },
{ "id": 3, "name": "Magneta" },
{ "id": 4, "name": "Tornado" }
]
創(chuàng)建一個(gè)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),
不用引入,引入之后會(huì)默認(rèn)使用web api;
然后hero.service.ts中這樣
private heroesUrl = 'assets/heroes.json';
其他的都是一樣的;
這種方式對(duì)于請(qǐng)求靜態(tài)的本地?cái)?shù)據(jù)是比較有用的;如果是請(qǐng)求真是的遠(yuǎn)程服務(wù)器地址,url換成遠(yuǎn)程地址就可以;


浙公網(wǎng)安備 33010602011771號(hào)