美文网首页
Angular 8 http 示例

Angular 8 http 示例

作者: SlowGO | 来源:发表于2019-12-28 19:11 被阅读0次

    创建项目

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

    相关文章

      网友评论

          本文标题:Angular 8 http 示例

          本文链接:https://www.haomeiwen.com/subject/nrasoctx.html