Angular 8 http 示例
创建项目
$ ng new ngstore
提示:
Would you like to add Angular routing?
输入 y
。
Which stylesheet format would you like to use?
选择 css
。
创建完成后,启动项目:
$ cd ngstore
$ ng serve
http://localhost:4200/ 可以访问项目。
image创建 JSON REST API
现在需要准备一个 json rest 接口服务器,提供一些假数据,供我们之后调用。
安装 json-server:
$ cd ~/ngstore
$ npm install -save json-server
在项目根目录下创建一个 server 目录:
$ mkdir server
$ cd server
创建数据文件 server/database.json
:
{
"products": []
}
安装 Fake.js :
$ cd ..
$ npm install faker -save
创建生成数据的文件 server/generate.js
:
var faker = require('faker');
var database = { products: [] };
for (var i = 1; i <= 300; i++) {
database.products.push({
id: i,
name: faker.commerce.productName(),
description: faker.lorem.sentences(),
price: faker.commerce.price(),
imageUrl: "https://source.unsplash.com/1600x900/?product",
quantity: faker.random.number()
});
}
console.log(JSON.stringify(database));
package.xml 中添加:
"scripts": {
...
"generate": "node ./server/generate.js > ./server/database.json",
"server": "json-server --watch ./server/database.json"
},
执行命令生成数据:
$ npm run generate
server/database.json
中可以看到已经生成了很多数据。
启动 json rest api server:
$ npm run server
启动信息:
> json-server --watch ./server/database.json
\{^_^}/ hi!
Loading ./server/database.json
Done
Resources
http://localhost:3000/products
Home
http://localhost:3000
Type s + enter at any time to create a snapshot of the database
Watching...
image
GET /products
获取所有 product 数据。
GET /products/<id>
根据 id 获取某条数据。
POST /products
创建一个新 product。
PUT /products/<id>
修改某个 product。
GET /products?_page=1
获取第一页数据。
GET /products?_page=1&_limit=5
获取第一页前5条数据。
安装 HttpClient 模块
打开 src/app/app.module.ts
添加 HttpClientModule
:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import{HttpClientModule}from'@angular/common/http'; // 添加
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule // 添加
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
创建组件
$ cd ~/ngstore
$ ng generate component home
$ ng generate component about
打开 src/app/about/about.component.html
, 添加内容:
<p style="padding:13px;">
An Angular 8 example application that demonstrates how to use HttpClient to consume \ REST APIs
</p>
添加路由
打开 src/app/app-routing.module.ts
添加:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home/home.component'; // 添加
import { AboutComponent } from './about/about.component'; // 添加
const routes: Routes = [
{ path: '', redirectTo: 'home', pathMatch: 'full' }, // 添加
{ path: 'home', component: HomeComponent }, // 添加
{ path: 'about', component: AboutComponent }, // 添加
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
使用 Material 定义 UI
$ ng add @angular/material
theme 选择 Indigo/Pink
。
? Set up HammerJS for gesture recognition? Yes
? Set up browser animations for Angular Material? Yes
打开 src/styles.css
添加:
@import "~@angular/material/prebuilt-themes/indigo-pink.css";
打开 src/app/app.module.ts
添加:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
// 添加
import {
MatToolbarModule,
MatIconModule,
MatCardModule,
MatButtonModule,
MatProgressSpinnerModule
} from '@angular/material';
@NgModule({
declarations: [
AppComponent,
HomeComponent,
AboutComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
BrowserAnimationsModule,
MatToolbarModule, // 添加
MatIconModule, // 添加
MatButtonModule, // 添加
MatCardModule, // 添加
MatProgressSpinnerModule // 添加
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
打开 src/app/app.component.html
更新为:
<mat-toolbar color="primary">
<h1>
ngStore
</h1>
<button mat-button routerLink="/">Home</button>
<button mat-button routerLink="/about">About</button>
</mat-toolbar>
<router-outlet></router-outlet>
通过 HttpClient 调用 JSON REST API
生成一个关联 JSON REST API 的服务接口:
$ ng generate service data
打开 src/app/data.service.ts
内容改为:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class DataService {
private REST_API_SERVER = "http://localhost:3000/products";
constructor(private httpClient: HttpClient) { }
public sendGetRequest() {
return this.httpClient.get(this.REST_API_SERVER);
}
}
在 app/home/home.component.ts
中引入 data service:
import { Component, OnInit } from '@angular/core';
import { DataService } from '../data.service'; // 添加
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
products = [];
constructor(private dataService: DataService) { } // 添加
ngOnInit() {
// 添加
this.dataService.sendGetRequest().subscribe((data: any[]) => {
console.log(data);
this.products = data;
})
}
}
app/home/home.component
改为:
<div style="padding:13px;">
<mat-spinner *ngIf="products.length === 0"></mat-spinner>
<mat-card *ngFor="let product of products" style="margin-top:10px;">
<mat-card-header>
<mat-card-title>{{product.name}}</mat-card-title>
<mat-card-subtitle>{{product.price}} $/ {{product.quantity}}
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<p>
{{product.description}}
</p>
<img style="height:100%; width: 100%;" src="{{ product.imageUrl }}" />
</mat-card-content>
<mat-card-actions>
<button mat-button> Buy product</button>
</mat-card-actions>
</mat-card>
</div>
image
添加 HTTP 错误处理
修改 src/app/data.service.ts
:
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http'; // 添加
import { throwError } from 'rxjs'; // 添加
import { retry, catchError } from 'rxjs/operators'; // 添加
@Injectable({
providedIn: 'root'
})
export class DataService {
private REST_API_SERVER = "http://localhost:3000/products";
constructor(private httpClient: HttpClient) { }
// 添加
handleError(error: HttpErrorResponse) {
let errorMessage = 'Unknown error!';
if (error.error instanceof ErrorEvent) {
// Client-side errors
errorMessage = `Error: ${error.error.message}`;
} else {
// Server-side errors
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
window.alert(errorMessage);
return throwError(errorMessage);
}
public sendGetRequest() {
return this.httpClient.get(this.REST_API_SERVER)
.pipe(catchError(this.handleError)); // 添加
}
}
关闭 json server,再次访问页面后,会报错:
image控制台也会输出错误日志:
imageHTTP 请求重试
有时请求出错只是因为暂时的网络原因,重试后就可以解决问题。
我们可以自动重试。
修改 src/app/data.service.ts
:
public sendGetRequest() {
return this.httpClient.get(this.REST_API_SERVER)
.pipe(retry(3),catchError(this.handleError)); // 添加
}
在 pipe
中添加了 retry(3)
。
添加请求参数
src/app/data.service.ts
中引入 HttpParams
:
import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
...
public sendGetRequest() {
const options = { params: new HttpParams({ fromString: "_page=1&_limit=20" }) };
return this.httpClient.get(this.REST_API_SERVER, options).pipe(retry(3), catchError(this.handleError));
}
分页
修改 src/app/data.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http'; // 添加
import { throwError } from 'rxjs'; // 添加
import { retry, catchError, tap } from 'rxjs/operators'; // 添加
@Injectable({
providedIn: 'root'
})
export class DataService {
public first: string = "";
public prev: string = "";
public next: string = "";
public last: string = "";
private REST_API_SERVER = "http://localhost:3000/products";
constructor(private httpClient: HttpClient) { }
parseLinkHeader(header) {
if (header.length == 0) {
return;
}
let parts = header.split(',');
var links = {};
parts.forEach(p => {
let section = p.split(';');
var url = section[0].replace(/<(.*)>/, '$1').trim();
var name = section[1].replace(/rel="(.*)"/, '$1').trim();
links[name] = url;
});
this.first = links["first"];
this.last = links["last"];
this.prev = links["prev"];
this.next = links["next"];
}
// 添加
handleError(error: HttpErrorResponse) {
let errorMessage = 'Unknown error!';
if (error.error instanceof ErrorEvent) {
// Client-side errors
errorMessage = `Error: ${error.error.message}`;
} else {
// Server-side errors
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
window.alert(errorMessage);
return throwError(errorMessage);
}
public sendGetRequest() {
return this.httpClient.get(this.REST_API_SERVER,
{ params: new HttpParams({ fromString: "_page=1&_limit=5" }), observe: "response" })
.pipe(retry(3), catchError(this.handleError), tap(res => {
console.log(res.headers.get('Link'));
this.parseLinkHeader(res.headers.get('Link'));
}));
}
public sendGetRequestToUrl(url: string) {
return this.httpClient.get(url, { observe: "response" }).pipe(retry(3), catchError(this.handleError), tap(res => {
console.log(res.headers.get('Link'));
this.parseLinkHeader(res.headers.get('Link'));
}));
}
}
修改 src/app/home/home.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { DataService } from '../data.service';
import { HttpResponse } from '@angular/common/http';
import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit, OnDestroy {
products = [];
destroy$: Subject<boolean> = new Subject<boolean>();
constructor(private dataService: DataService) { }
ngOnInit() {
this.dataService.sendGetRequest().subscribe((res: HttpResponse<any>) => {
console.log(res);
this.products = res.body;
})
}
ngOnDestroy() {
this.destroy$.next(true);
// Unsubscribe from the subject this.destroy$.unsubscribe();
}
public firstPage() {
this.products = [];
this.dataService.sendGetRequestToUrl(this.dataService.first).pipe(takeUntil(this.destroy$)).subscribe((res: HttpResponse<any>) => {
console.log(res);
this.products = res.body;
})
}
public previousPage() {
if (this.dataService.prev !== undefined && this.dataService.prev !== '') {
this.products = [];
this.dataService.sendGetRequestToUrl(this.dataService.prev).pipe(takeUntil(this.destroy$)).subscribe((res: HttpResponse<any>) => {
console.log(res); this.products = res.body;
})
}
}
public nextPage() {
if (this.dataService.next !== undefined && this.dataService.next !== '') {
this.products = [];
this.dataService.sendGetRequestToUrl(this.dataService.next).pipe(takeUntil(this.destroy$)).subscribe((res: HttpResponse<any>) => {
console.log(res);
this.products = res.body;
})
}
}
public lastPage() {
this.products = [];
this.dataService.sendGetRequestToUrl(this.dataService.last).pipe(takeUntil(this.destroy$)).subscribe((res: HttpResponse<any>) => {
console.log(res);
this.products = res.body;
})
}
}
修改 src/app/home/home.component.html
<div style="padding:13px;">
<mat-spinner *ngIf="products.length === 0"></mat-spinner>
<mat-card *ngFor="let product of products" style="margin-top:10px;">
<mat-card-header>
<mat-card-title>#{{product.id}} {{product.name}}</mat-card-title>
<mat-card-subtitle>{{product.price}} $/ {{product.quantity}}
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<p>
{{product.description}}
</p>
<img style="height:100%; width: 100%;" src="{{ product.imageUrl }}" />
</mat-card-content>
<mat-card-actions>
<button mat-button> Buy product</button>
</mat-card-actions>
</mat-card>
</div>
<div>
<button (click)="firstPage()" mat-button> First</button>
<button (click)="previousPage()" mat-button> Previous</button>
<button (click)="nextPage()" mat-button> Next</button>
<button (click)="lastPage()" mat-button> Last</button>
</div>
image
参考:
https://www.freecodecamp.org/news/angular-8-tutorial-in-easy-steps/