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
已是根模塊AppModule
的imports
數組的一部分了;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
對象做爲參數,該對象中包含 next
、error
和 complete
方法在 RxJS 中,返回的是 Subcription
對象,該對象中包含一個 unsubscribe
方法。
一個 Observable 對象設置觀察者 (observer),並將它與生產者關聯起來。該生產者多是 DOM 元素產生的 click
或 input
事件,也多是更復雜的事件,如 HTTP。
當 Observable 對象產生新值的時候,咱們能夠經過調用 next()
方法來通知對應的觀察者。若出現異常,則會調用觀察者的 error()
方法。當咱們訂閱 Observable 對象後,只要有新的值,都會通知對應的觀察者。但在如下兩種狀況下,新的值不會再通知對應的觀察者:
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換成遠程地址就能夠;