如何在Angular中使用Observables进行HTTP请求

424 阅读5分钟

为了在Angular中获取数据,我们可以使用Observables与http来获取数据。一个Angular应用程序将通过GET请求使用http来从后端服务器获取数据。后端可以由任何数量的服务器端技术驱动,如Java、PHP、Python,甚至是Node.js形式的服务器端JavaScript。一个http响应从后端网络服务发送到客户端的angular应用程序。在本教程中,我们将学习诸如Observables、Reactive Extensions、HTTP Requests等内容,以及它们如何集成到angular应用程序中。


观察变量和反应式扩展

Reactive Extensions,即RxJS,将数据序列表示为一个可观察序列。可观察序列被简单地称为 "可观察"。Observable的目的是管理异步数据,比如你在使用HTTP等待数据通过网络到达时可能得到的数据。一个Observable将事件视为一个集合。认为Observable的一种方式是一个数组,其项目随着时间的推移而异步地填充。在一个Observable中,你会有一个方法来订阅接收新数据到来的通知。Angular框架使用Observables来完成其异步工作。


Angular可观察变量

在事件处理、异步编程和处理多个值方面,Observables比其他技术有很大的优势。观察者是声明性的,也就是说,你定义了一个用于发布值的函数,但在消费者订阅它之前,它不会被执行。


可观察的操作符

像map、filter、take和merge这样的函数是可观察操作符的例子。它们在处理每一个被发射出来的值时,会转换源可观察变量并组成一个新的可观察变量。更好地理解可观察变量的一个方法是熟悉大理石图。在这里,我们看到一个map函数的例子,它将每个值转换为其原始状态的10倍。
rx observable of map function


如何组成可观察操作符

可观察操作符是用一种被称为Pipeable Operators的管道方法组成的。下面是一个例子。

import {Observable, range} from 'rxjs';
import {map, filter} from 'rxjs/operators';

const source$: Observable<number> = range(0, 10);

source$.pipe(
    map(x => x * 2),
    filter(x => x % 3 === 0)
).subscribe(x => console.log(x));

在控制台中的输出会是这样的。

0

6

12

18

那么在上面的代码中会发生什么呢?好吧,首先我们在使用Observables时导入两个常见的包。那就是rxjsrxjs/operators包。然后,const source$的那一行。Observable = range(0, 10)创建了一个 0 到 10 范围内的可观察数字流。注意美元符号的后缀,它表示一个特定的变量正持有一个Observable。对于现在持有可观察变量的任何变量,注意我们现在可以使用 **.pipe()**方法传入一个或多个操作函数,这些操作函数可以处理和转换可观察集合中的每个项目。因此,这个例子将0到10范围内的每个数字乘以2,然后,我们使用过滤函数将结果过滤到只剩下奇数。一旦完成,我们就会订阅到观测器,并记录下结果。非常酷!"。


诺言与可观察操作符

当一个异步操作完成或失败时,Promise用于处理一个单一的事件,而Observable就像一个Stream,允许传递零个或多个事件,每个事件的回调都被调用。这里有一个表格,显示了其中的一些区别。

承诺

可观察的

提供一个单一的未来值
在一段时间内发出多个值
非懒惰
懒惰
不能取消
能够取消
可以与map、filter、reduce和其他操作符一起使用

在Angular中发送HTTP请求

现在我们对Observables有了一定的了解,让我们来配置我们的游戏应用,以便从API中获取游戏数据。


导入HttpClientModule

首先,我们需要在应用程序中导入HttpClientModule模块。我们可以像这样在app.module.ts中做这件事。

import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {FormsModule} from '@angular/forms';
import {HttpClientModule} from '@angular/common/http';

import {AppComponent} from './app.component';
import {GameListComponent} from './games/game-list.component';
import {ThumbComponent} from './shared/thumb.component';

@NgModule({
    declarations: [
        AppComponent,
        GameListComponent,
        ThumbComponent
    ],
    imports: [
        BrowserModule,
        FormsModule,
        HttpClientModule
    ],
    bootstrap: [AppComponent]
})
export class AppModule {
}

在游戏服务中使用Http客户端

Angular服务依赖注入教程中,我们将游戏数据直接硬编码到game.service.ts文件中。现在,我们可以删除这些数据,并更新服务,使其向网络服务器发出HTTP请求以获取数据。下面我们强调了更新后的代码,其中我们导入了我们需要的模块,将HTTP模块注入构造函数中,设置了一个API网址来发送请求,并定义了getGames()和handleError()方法。

import {Injectable} from '@angular/core';
import {HttpClient, HttpErrorResponse} from '@angular/common/http';
import {Observable, throwError} from 'rxjs';
import {catchError, tap, map} from 'rxjs/operators';

import {IGame} from './game';

@Injectable({
    providedIn: 'root'
})
export class GameService {
    private gameUrl = 'http://lpg.io/api/angulargames';

    constructor(private http: HttpClient) {
    }

    getGames(): Observable<IGame[]> {
        return this.http.get<IGame[]>(this.gameUrl).pipe(
            tap(data => console.log('All: ' + JSON.stringify(data))),
            catchError(this.handleError)
        );
    }

    private handleError(err: HttpErrorResponse) {

        let errorMessage = '';
        if (err.error instanceof ErrorEvent) {

            errorMessage = `An error occurred: ${err.error.message}`;
        } else {

            errorMessage = `Server returned code: ${err.status}, error message is: ${err.message}`;
        }
        console.error(errorMessage);
        return throwError(errorMessage);
    }
}

配置服务器端API

对于这个演示,我们将HTTP请求发送[到http://lpg.io/api/angulargames,这是一个运行在虚拟机上的服务器。这台服务器使用的是Laravel,这是一个流行的PHP框架,非常能够提供专门的JSON API网络服务功能。下面是我们在服务器端使用Laravel的代码。


api.php

<?php

use IlluminateHttpRequest;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::get('/user', function (Request $request) {
    return $request->user();
})->middleware('auth:api');

Route::get('/angulargames', function () {
    return [["gameId" => 1,
        "gameName" => "Castlevania",
        "gameCode" => "AAA-0101",
        "releaseDate" => "September 26, 1986",
        "description" => "Action-adventure game series created and developed by Konami.",
        "price" => 19.99,
        "thumbRating" => 5.0,
        "imageUrl" => "./assets/images/castlevania.png"
    ],
        [
            "gameId" => 2,
            "gameName" => "Dr Mario",
            "gameCode" => "AAA-1100",
            "releaseDate" => "July 27, 1990",
            "description" => "Action puzzle game produced by Gunpei Yokoi and published by Nintendo.",
            "price" => 15.99,
            "thumbRating" => 3,
            "imageUrl" => "./assets/images/drmario.png"
        ],
        [
            "gameId" => 3,
            "gameName" => "Kid Icarus",
            "gameCode" => "AAA-0048",
            "releaseDate" => "December 19, 1986",
            "description" => "Kid Icarus revolves around protagonist Pit's quest for three sacred treasures.",
            "price" => 20.99,
            "thumbRating" => 4,
            "imageUrl" => "./assets/images/kidicarus.png"
        ],
        [
            "gameId" => 4,
            "gameName" => "Legend Of Zelda",
            "gameCode" => "AAA-0049",
            "releaseDate" => "February 21, 1986",
            "description" => "Link is often given the task of rescuing Princess Zelda and the kingdom of Hyrule from Ganon.",
            "price" => 29.95,
            "thumbRating" => 5,
            "imageUrl" => "./assets/images/legendofzelda.png"
        ],
        [
            "gameId" => 5,
            "gameName" => "Mega Man",
            "gameCode" => "AAA-1042",
            "releaseDate" => "December 17, 1987",
            "description" => "Mega Man is an android originally named Rock, created as a lab assistant by the scientist Dr. Light.",
            "price" => 15.95,
            "thumbRating" => 4.5,
            "imageUrl" => "./assets/images/megaman.png"
        ],
        [
            "gameId" => 6,
            "gameName" => "Metroid",
            "gameCode" => "AAA-4511",
            "releaseDate" => "August 6, 1986",
            "description" => "Metroid follows space-faring bounty hunter Samus Aran, who protects the galaxy from the Space Pirates.",
            "price" => 10.95,
            "thumbRating" => 3,
            "imageUrl" => "./assets/images/metroid.png"
        ],
        [
            "gameId" => 7,
            "gameName" => "Super Mario Bros",
            "gameCode" => "AAA-3325",
            "releaseDate" => "September 13, 1985",
            "description" => "Mario finds power-ups and items that give him special magic powers such as fireball-throwing and size-changing into giant and miniature sizes.",
            "price" => 40.95,
            "thumbRating" => 5,
            "imageUrl" => "./assets/images/supermariobros.png"]];
});

**注意:**你还需要在publicindex.php中添加以下代码段,以启用CORS功能。如果你不包括这些,你可能会遇到错误,如服务器返回代码。0,错误信息是。Http failure response for (unknown url):0 未知的错误,或者可能是类似这样的错误:从原点'http://localhost:4200'访问 XMLHttpRequest 被 CORS 政策阻止了。请求的资源上没有'Access-Control-Allow-Origin'头。

header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, PUT, POST, DELETE, OPTIONS');

更新游戏列表组件

现在一切就绪,我们可以更新game-list.component.ts文件。具体来说,我们将为 ngOnInit()生命周期钩子的新代码,这样它就可以利用Observables向我们的api发出http get请求,使用我们更新的游戏服务。

import {Component, OnInit} from '@angular/core';
import {IGame} from './game';
import {GameService} from './game.service';

@Component({
    selector: 'game-list',
    templateUrl: './game-list.component.html',
    styleUrls: ['./game-list.component.css']
})
export class GameListComponent implements OnInit {
    pageTitle = 'Dynamic! Game List';
    imageWidth = 45;
    imageMargin = 1;
    showImage = true;
    _listFilter = '';
    get listFilter(): string {
        return this._listFilter;
    }

    set listFilter(value: string) {
        this._listFilter = value;
        this.filteredGames = this.listFilter ? this.doFilter(this.listFilter) : this.games;
    }

    filteredGames: IGame[] = [];
    games: IGame[] = [];

    constructor(private gameService: GameService) {
        this.listFilter = '';
    }

    onRatingClicked(message: string): void {
        this.pageTitle = 'Game List: ' + message;
    }

    doFilter(filterBy: string): IGame[] {
        filterBy = filterBy.toLocaleLowerCase();
        return this.games.filter((game: IGame) =>
            game.gameName.toLocaleLowerCase().indexOf(filterBy) !== -1);
    }

    toggleImage(): void {
        this.showImage = !this.showImage;
    }
    ngOnInit(): void {
        this.gameService.getGames().subscribe(
            games => {
                this.games = games;
                this.filteredGames = this.games;
            },
            error => this.errorMessage = <any>error
        );
    }
}

我们新的游戏服务使用Observables来向Laravel后端服务器发出HTTP请求,现在已经开始工作了!
angular http request via observable example

你也可以在chrome开发工具的控制台部分看到API响应数据。
inpecting angular api response data in console


如何在Angular中使用Observables进行HTTP请求总结

以下是在Angular中处理Observables和HTTP请求时需要记住的一些关键点。

在Angular中设置HTTP

  • 将HttpClientModule添加到应用程序中Angular模块的导入数组中。
  • 导入数据服务所需的内容
  • 使用构造函数为http客户端服务定义一个依赖关系
  • 为你想使用的每种http请求类型创建一个方法
  • 根据需要调用http方法(例如:GET)
  • 使用泛型来指定返回类型

订阅可观察变量

  • 调用返回的可观察对象的订阅方法
  • 创建一个函数来处理发射的项目
  • 将属性数据分配给返回的JSON对象
  • 创建一个函数来处理错误