Angular 8 http 示例

2019-12-28  本文已影响0人  SlowGO

创建项目

$ 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...

访问 http://localhost:3000/

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

控制台也会输出错误日志:

image

HTTP 请求重试

有时请求出错只是因为暂时的网络原因,重试后就可以解决问题。

我们可以自动重试。

修改 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/

上一篇下一篇

猜你喜欢

热点阅读