在Angular 7中,我如何从一个Observable中提取结果?

In Angular 7, how do I extract the result from an Observable?

我正在使用 Angular 7 和 Rail 5 后端。我有一个用于与 Rails 5 后端交互的服务 ...

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';

@Injectable()
export class CurrencyService {
  constructor(private _http: HttpClient) { }

  index() {
    let ret = this._http.get<Object[]>('api/currencies').map(r => r);
    console.log(ret);
    return ret;
  }

  refresh() {
    return this._http.get<Object[]>('api/refresh').map(r => r);
  }
}

我的 src/app/app.component.ts 文件中有这个与服务交互...

import { CurrencyService } from './../shared/currency.service';
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent {
  currencies = [];
  title = 'app';
  apiStatus: string;
  constructor(private _currencySrv: CurrencyService) { }

  ngOnInit() {
    this._currencySrv.index<Object[]>().subscribe(
      currencies => this.currencies = currencies);
  }

  refresh() {
    this._currencySrv.refresh<Object[]>().subscribe(
      currencies => this.currencies = currencies);
  }

}

如何从返回的 Observable 对象中提取对 "index" 和 "refresh" 的调用结果?当我尝试使用 ngFor 遍历 "currencies" 成员变量时,出现错误

ERROR TypeError: Cannot read property 'name' of undefined
    at Object.eval [as updateRenderer] (AppComponent.html:4)
    at Object.debugUpdateRenderer [as updateRenderer] (core.js:14735)
    at checkAndUpdateView (core.js:13849)
    at callViewAction (core.js:14195)
    at execComponentViewsAction (core.js:14127)
    at checkAndUpdateView (core.js:13850)
    at callWithDebugContext (core.js:15098)
    at Object.debugCheckAndUpdateView [as checkAndUpdateView] (core.js:14635)
    at ViewRef_.detectChanges (core.js:11619)
    at eval (core.js:5918)

我认为是因为结果包含在一个名为 "data" 的字段中,但是当我尝试启动 Angular 时,我不知道在没有各种编译错误的情况下提取该字段的正确方法] 服务器通过 "ng serve".

编辑: 在 src/app/app.component.html 我有

<!--The content below is only a placeholder and can be replaced.-->
<ul>
  <ng-container *ngIf="currencies?.length">
  <li *ngFor="let currency of currencies">
    <div>{{currency.name}}</div>
    <div>{{currency.country}}</div>
    <div>{{currency.rate / 100}}</div>
  </li>
  </ng-container>
</ul>

刷新

尽管如此

,但从未显示任何数据

那是因为currencies还没有填充数据。在请求完成之前呈现模板。将列表包装在 <ng-container *ngIf=“currencies?.length”> 中会有所帮助。

JB Nizet 在评论中写的一切都是绝对真实的。

更新:

app.component.ts

import { CurrencyService } from './../shared/currency.service';
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent implements OnInit {
  currencies = [];
  title = 'app';
  apiStatus: string;

  constructor(private _currencySrv: CurrencyService) {
  }

  ngOnInit() {
    this._currencySrv.index().subscribe(
      currencies => {
        console.log(currencies);
        this.currencies = currencies;
      });
  }

  refresh() {
    this._currencySrv.refresh().subscribe(currencies => this.currencies = currencies);
  }
}

currency.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import 'rxjs/add/operator/map';
import { map } from 'rxjs/operators';

export interface CurrencyWrapper {
  data: Currency[];
}

export interface Currency {
  name: string,
  country: string,
  rate: number,
}

@Injectable()
export class CurrencyService {
  constructor(private _http: HttpClient) { }

  index() {
    return this._http.get<CurrencyWrapper>('api/currencies').pipe(map(currencies => currencies.data));
  }

  refresh() {
    return this._http.get<CurrencyWrapper>('api/refresh').pipe(map(currencies => currencies.data));
  }
}

注意:最好在单独的文件中定义接口。您也可以根据需要重构代码。

由于结果被包装在一个对象中 'data',您需要做的就是在 observable 发出值时分配您需要的对象。

  ngOnInit() {
    this._currencySrv.index<Object[]>().subscribe(
      currencies => this.currencies = currencies.data);
  }
  refresh() {
    this._currencySrv.refresh<Object[]>().subscribe(
      currencies => this.currencies = currencies.data);
  }

您的代码中有很多问题需要更正。 @JBNizet 似乎已经提到了大部分问题。

让我们先从您的服务开始。

  • 如前所述,如果您在 Angular 应用程序中使用 rxjs6,则可以使用 rxjs6 管道运算符语法代替您使用的 rxjs5 语法. (来源:docs
  • Observableimport 位置更改为 rxjs,将 map 的位置更改为 rxjs/operators。 (来源:docs
  • import { Http } from '@angular/http'; 是多余的。此软件包已弃用。请改用 @angular/common/http。 (来源:docs
  • 不要使用 Object,因为它指的是非原始盒装对象。 (来源:docs)。而是使用 any 或更好的方法,创建一个定义后端响应的接口。
  • 你的 map 目前的写法,它甚至没有做任何事情。相反,由于您的后端 return 是一个包含 data 的对象,您可以使用 map 到 return 只是 data.
  • 我不确定你的 refresh api 做了什么,但如果它只是得到与 currencies api 相同的数据,那么它似乎没有必要。我将保留它在我的回答中的原样,因为我不确定它是否包含其他数据。

考虑到所有这些,您的 CurrencyService 现在看起来像

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { CurrenciesResponse, Currency } from 'path/to/your/interface';

@Injectable()
export class CurrencyService {
    constructor(
        private _http: HttpClient
    ) { }

    index(): Observable<Currency[]> {
        return this._http.get<CurrenciesResponse>('api/currencies').pipe(
            map((res: CurrenciesResponse) => res.data)
        );
    }

    refresh(): Observable<Currency[]> {
        return this._http.get<CurrenciesResponse>('api/refresh').pipe(
            map((res: CurrenciesResponse) => res.data)
        );
    }
}

您的界面看起来像这样

export interface CurrenciesResponse {
    data: Currency[];
    // Add other backend response properties here if present
}

export interface Currency {
    name: string;
    country: string;
    rate: number;
    // ...
}

将您的界面添加到单独的文件中,并在必要时导入它们。

在您的组件中,似乎只有一个问题。从 currencyService 调用 index()refresh() 时不需要提供类型参数,因为它们不是通用方法。您的 IDE 通常会通过 Expected 0 type arguments, but got 1

之类的错误警告您
currencies: Currency[] = [];
title: string = 'app';
apiStatus: string;

constructor(
    private _currencySrv: CurrencyService
) { }

ngOnInit() {
    this._currencySrv.index().subscribe(currencies => {
        this.currencies = currencies;
    });
}

refresh() {
    this._currencySrv.refresh().subscribe(currencies => {
        this.currencies = currencies;
    });
}

最后,在您的 HTML 中,ngIf 是多余的,因为即使 ngFor 也不会打印任何内容,除非 currencies 包含数据。

<ul *ngFor="let currency of currencies">
    <li>
        <div>{{currency.name}}</div>
        <div>{{currency.country}}</div>
        <div>{{currency.rate / 100}}</div>
    </li>
</ul>

这是 StackBlitz. I have used angular-in-memory-web-api 上 API 响应的工作示例。

只需要做几个步骤:

不要将结果映射到未定义的对象中。

let ret = this._http.get<Object[]>('api/currencies').map(r => r);

这不是好的做法。更好的是创建一个模型。然后 TypeScript 将知道对象中有哪些属性。看看这个:

export class CurrencyReponse {   
   name: string;  
   country: string;  
   rate: number; 
}

然后在导入后请使用:

index(): Observable<CurrencyReponse[]> {
   return this.http.get<CurrencyReponse[]>('api/currencies');
}

刷新方法相同。

然后在组件中消费时:

  ngOnInit() {
    this._currencySrv.index<CurrencyReponse[]>().subscribe(
      response => this.currencies = response.data);
  }

  refresh() {
    this._currencySrv.refresh<CurrencyReponse[]>().subscribe(
      response => this.currencies = response.data);
  }

奖金提示

在这里你被 *ngFor 循环保护,但有时如果你没有它并且你想显示一些可以稍后初始化的东西那么你可以使用 *ngIf 条件。例如:

    <div *ngIf="currency.name">{{currency.name}}</div>
    <div *ngIf="currency.country">{{currency.country}}</div>
    <div *ngIf="currency.rate">{{currency.rate / 100}}</div>