Get data from a server
- 在本教程中,您将在Angular的HttpClient的帮助下添加以下数据持久性特性。
- HeroService通过HTTP请求获取英雄数据。用户可以通过HTTP添加、编辑和删除英雄并保存这些更改。
- 用户可以通过名字搜索英雄。
一、 启用HTTP服务
-
HttpClient是Angular用来通过HTTP与远程服务器通信的机制。
-
通过两个步骤使应用程序中的所有地方都可以使用HttpClient。首先,通过导入将其添加到根AppModule:
```
<-src/app/app.module.ts (HttpClientModule import)->
import { HttpClientModule } from '@angular/common/http';
```
-
*:*接下来,仍然在AppModule中,将HttpClient添加到导入数组中:
<-src/app/app.module.ts (imports array excerpt)-> @NgModule({ imports: [ HttpClientModule, ], })
二、模拟数据服务器
-
本教程示例使用内存中的Web API模块模拟与远程数据服务器的通信。
-
安装模块后,应用程序将向HttpClient发出请求并接收响应,而不知道内存中的Web API正在拦截这些请求,将它们应用到内存中的数据存储中,并返回模拟的响应。
-
通过使用内存中的Web API,您不必设置服务器来了解HttpClient。
重要提示:在Angular中,内存中的Web API模块与HTTP无关。 如果您只是阅读本教程以了解HttpClient,那么您可以跳过这一步。如果您正在编写本教程中的代码,请留在这里并立即添加内存中的Web API。
-
使用以下命令从npm安装内存中的Web API包:
npm install angular-in-memory-web-api --save
-
在AppModule中,导入HttpClientInMemoryWebApiModule和稍后将创建的InMemoryDataService类。
<-src/app/app.module.ts (In-memory Web API imports)-> import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api'; import { InMemoryDataService } from './in-memory-data.service';
-
在HttpClientModule之后,将HttpClientInMemoryWebApiModule添加到AppModule import数组中,并使用InMemoryDataService配置它。
<-src/app/app.module.ts (imports array excerpt)-> HttpClientModule, // HttpClientInMemoryWebApiModule模块拦截HTTP请求 // 返回模拟的响应数据 // 在真正的服务器准备接收请求时删除它 HttpClientInMemoryWebApiModule.forRoot( InMemoryDataService, { dataEncapsulation: false } )
-
forRoot()配置方法接受一个InMemoryDataService类,该类启动内存中的数据库。
-
生成类src/app/in-memory-data.service。ts使用以下命令:
```
ng generate service InMemoryData
```
-
用in-memory-data.service.ts的内容代替默认内容:
<-src/app/in-memory-data.service.ts-> import { InMemoryDbService } from 'angular-in-memory-web-api'; import { Hero } from './hero'; import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class InMemoryDataService implements InMemoryDbService { createDb() { const heroes = [ { id: 11, name: 'Dr Nice' }, { id: 12, name: 'Narco' }, { id: 13, name: 'Bombasto' }, { id: 14, name: 'Celeritas' }, { id: 15, name: 'Magneta' }, { id: 16, name: 'RubberMan' }, { id: 17, name: 'Dynama' }, { id: 18, name: 'Dr IQ' }, { id: 19, name: 'Magma' }, { id: 20, name: 'Tornado' } ]; return {heroes}; } // 重写genId方法以确保英雄始终具有id。 // 如果heroes数组是空的, // 下面的方法返回初始数字(11)。 // 如果heroes数组不是空的,则下面的方法返回最高的值 // 英雄id + 1。 genId(heroes: Hero[]): number { return heroes.length > 0 ? Math.max(...heroes.map(hero => hero.id)) + 1 : 11; } }
in-memory-data.service.ts文件代替了
mock-heroes.ts
,现在可以安全删除了。当服务器准备好时,您将分离内存中的Web API,应用程序的请求将经过服务器。
三、Heroes and HTTP
-
在HeroService中,导入HttpClient和HttpHeaders:
<-src/app/hero.service.ts -> import { HttpClient, HttpHeaders } from '@angular/common/http';
-
仍然在HeroService中,将HttpClient注入到名为http的私有属性的构造函数中。
<-src/app/hero.service.ts-> constructor( private http: HttpClient, private messageService: MessageService) { }
-
注意,您一直在注入MessageService,但是由于您会频繁地调用它,所以将它封装在一个私有log()方法中:
<-src/app/hero.service.ts-> /** Log a HeroService message with the MessageService */ private log(message: string) { this.messageService.add(`HeroService: ${message}`); }
-
使用服务器上英雄资源的地址来定义表单的heroesUrl:base/:collectionName。这里base是发出请求的资源,而collectionName是 in-memory-data-service.ts 中的heroes数据对象。
<-src/app/hero.service.ts-> private heroesUrl = 'api/heroes'; // URL to web api
四、Get heroes with HttpClient
-
当前的HeroService.getHeroes()使用()函数的RxJS来返回一个模拟英雄数组,作为一个可观察到的<Hero[]>。
<-src/app/hero.service.ts (getHeroes with RxJs 'of()')-> getHeroes(): Observable<Hero[]> { return of(HEROES); }
-
转换该方法使用HttpClient如下:
<-src/app/hero.service.ts-> /** GET heroes from the server */ getHeroes (): Observable<Hero[]> { return this.http.get<Hero[]>(this.heroesUrl) }
- 刷新浏览器。应该可以成功地从模拟服务器加载英雄数据。
- 您已经将of()交换为http.get(),应用程序在没有任何其他更改的情况下继续工作,因为两个函数都返回一个可观察到的<Hero[]>。
HttpClient
methods return one value
所有HttpClient方法都返回一个可观察到的RxJS。
HTTP是一个请求/响应协议。你发出一个请求,它会返回一个响应。
一般来说,一个被观察对象可以在一段时间内返回多个值。来自HttpClient的一个可观察对象总是发出一个单独的值,然后完成,不再发出。
这个特定的HttpClient.get()调用返回一个可观察的<Hero[]>;也就是说,“一个可见的英雄数组”。实际上,它只会返回一个英雄数组。
HttpClient.get()
returns response data
-
默认情况下,HttpClient.get()以无类型JSON对象的形式返回响应主体。应用可选的类型说明符<Hero[]>,增加了TypeScript功能,减少了编译时的错误。
-
服务器的数据API决定JSON数据的形状。英雄指南数据API以数组的形式返回英雄数据。
```
其他api可能会将您想要的数据隐藏在对象中。您可能需要使用RxJS map()操作符来处理可观察结果,从而挖掘出数据。
虽然这里没有讨论,但是示例源代码中包含了getHeroNo404()方法中的map()示例。
```
Error handling
-
事情会出错,特别是当您从远程服务器获取数据时。getheroes()方法应该能捕获错误并执行适当的操作。
-
为了捕获错误,您可以通过RxJS的catchError()操作符从http.get()“管道”传输观察到的结果。
-
从rxjs/操作符以及稍后需要的其他操作符中导入catch错误符号。
```
<-src/app/hero.service.ts->
import { catchError, map, tap } from 'rxjs/operators';
```
-
现在使用pipe()方法扩展observable结果,并给它一个catchError()操作符。
<-src/app/hero.service.ts-> getHeroes (): Observable<Hero[]> { return this.http.get<Hero[]>(this.heroesUrl) .pipe( catchError(this.handleError<Hero[]>('getHeroes', []))//第一个参数是被监察的函数,第二个是返回的对象 ); }
-
操作符catchError()截获一个失败的观察目标。它向错误传递一个错误处理程序,该处理程序可以对错误做它想做的事情。
- 下面的handleError()方法报告错误,然后返回一个无害的结果,以便应用程序继续工作。
handleError
-
下面的handleError()将被许多HeroService方法共享,因此它被一般化以满足不同的需求。
-
它不是直接处理错误,而是返回一个错误处理函数来捕获它配置的错误,该函数使用失败操作的名称和一个安全的返回值。
```
<-src/app/hero.service.ts->
/**
* 处理失败的Http操作。
* 让应用程序继续。
* @参数操作 - 失败操作的名称
* @参数结果 - 可选值作为可观察结果返回
*/
private handleError<T> (operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
// TODO:将错误发送到远程日志记录基础设施
console.error(error); // 改为登录控制台
// TODO:更好地转换用户使用的错误
this.log(`${operation} failed: ${error.message}`);
// 通过返回一个空结果让应用程序继续运行。
return of(result as T);
};
}
```
-
在向控制台报告错误之后,处理程序构造一个用户友好的消息并向应用程序返回一个安全值,以便应用程序可以继续工作。
- 因为每个服务方法都返回一种不同类型的可观察结果,所以handleError()接受一个类型参数,这样它就可以返回应用程序所期望的类型的安全值。
Tap into the Observable
-
HeroService方法将进入可观察值流,并通过log()方法将消息发送到页面底部的消息区域。
-
他们将使用RxJS tap()操作符来完成这些操作,该操作符查看可观察值,对这些值执行一些操作,并将它们传递下去。tap()回调不会触及值本身。
-
下面是getHeroes()的最终版本,带有记录操作的tap()。
```
<-src/app/hero.service.ts->
/** GET heroes from the server */
getHeroes (): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
.pipe(
tap(_ => this.log('fetched heroes')),
catchError(this.handleError<Hero[]>('getHeroes', []))
);
}
```
Get hero by id
-
大多数web api都支持以下形式的get by id请求:baseURL/:id。
-
这里,基本URL是在Heroes和HTTP部分(api/ Heroes)中定义的heroesURL, id是您想要检索的英雄的编号。例如,api /英雄/ 11。
-
使用以下方法更新HeroService getHero()方法以发出请求:
```
<-src/app/hero.service.ts->
/** GET hero by id. Will 404 if id not found */
getHero(id: number): Observable<Hero> {
const url = `${this.heroesUrl}/${id}`;
return this.http.get<Hero>(url).pipe(
tap(_ => this.log(`fetched hero id=${id}`)),
catchError(this.handleError<Hero>(`getHero id=${id}`))
);
}
```
-
与getHeroes()有三个显著的区别:
getHero()使用所需的英雄id构造一个请求URL。 服务器应该使用单个英雄响应,而不是一组英雄。 getHero()返回一个可观察到的<Hero>(“一个可观察到的Hero对象”),而不是一个可观察到的Hero数组。
Update heroes
-
在英雄详情视图中编辑一个英雄的名字。当您键入时,英雄名称将更新页面顶部的标题。但是当你点击“返回”按钮时,改变就会丢失。
-
如果希望更改保持不变,则必须将它们写回服务器 。
-
在hero detail模板的末尾,添加一个带有click事件绑定的save按钮,该按钮调用一个名为save()的新组件方法。
```
<-src/app/hero-detail/hero-detail.component.html (save)->
<button (click)="save()">save</button>
```
-
在HeroDetail组件类中,添加以下save()方法,该方法使用hero服务updateHero()方法持久保存英雄的名字更改,然后导航回之前的视图。