Angular `HttpClient` `.get()` 泛型可以有非简单的属性类型吗?(例如,除了 `string` 或 `number`)

IT技术 angular reactjs typescript httpclient
2021-05-18 14:41:48

我花了一天时间研究这个主题,但我没有找到任何线索。我发现的只是omission,因为我读过的任何内容都没有说明您可以在调用 Angular 4.4 时将非简单类型(我的意思是stringnumber)用于通用接口(“形状”)HttpClient.<T>get()

而不是未能描述问题,下面的示例代码是我想要做的:

// Injected in my real code ... but these are the libs being used.
import { HttpClientModule } from '@angular/common/http';
import { Observable } from "rxjs";
import 'rxjs/Rx';

interface IMyDataItem {
    // In this example, using @Types/Decimal.js
    // It appears the "problem" exists for any types beside the simples (number/string etc)
    results: decimal.Decimal[];
}

class Service {
   doSomethingCool() {
       this._http
          .get<IMyDataItem>("url://somewhere")
          .subscribe(
             data => {
                // Call a method on the Decimal typed property
                data.results[0].pow(3, 3);
             }
          );
   }
}

它在语法上编译。TypeScript 正确地解析了泛型魔法——并识别出它data.results是一个类型为 的数组Decimal

然而,在运行时(在 Chrome 中,在转换为 Javascript 之后)调用pow()失败,理由是该方法不存在(实际上,调试它表明它是一个普通的旧 Javascript 对象)。

我对 TypeScript 和 Angular 4 很陌生——但这对我来说是合乎逻辑的。我只要求通用get()调用将请求作为接口处理。它必须确定如何构造满足该接口的对象。我没有在任何地方构建实现该接口的实体对象,也没有在任何地方构建Decimal.

(更新:这篇文章进一步让我相信我的直觉...... https://jameshenry.blog/typescript-classes-vs-interfaces/

官方文件(实际上我观察每一个非官方源)表明,它与(在我而言)的“简单”类型(特别是工作numberstring) -这我的直觉说“有道理”,因为一个JSON对象可以有简单的字符串和简单的数字。

我错过了明显的吗?或者直接映射到更复杂的类类型是不可能的?

如果这是不可能的 - 你通常如何处理它?我一直在寻找“最佳实践”模式——结果很短。

再次,我的直觉(并且只有我的直觉说我应该在处理 API 请求的类中拥有一个私有方法 - 并返回一个Observable(调用者将订阅的)它给出一个复杂的对象类型(例如一个User数组,其中包含与用户相关的方法):

class Service {
   // Return a complex object type Observable for consumption elsewhere
   doSomethingCool(): Observable<IMyDataItem> {
       return this._http
          .get<IMyDataItem>("url://somewhere");
   }
}

那有意义吗?TIA。

4个回答

http.get()的 Angular 返回一个Response. 首先尝试将其映射到 JSON:

import "rxjs/add/operator/map";

....
class Service {
    doSomethingCool(): Observable<IMyDataItem> {
        return this._http
            .get("url://somewhere");
            .map((response:Response) => response.json() as IMyDataItem);
    }
}

ps 需要导入rxjs才能使用map()

编辑

在对评论进行讨论之后,我对这个“AngularHttpClient .get()泛型是否可以具有非简单属性类型”问题的回答是:

不,因为默认get()方法默认返回 JSON。并且基于该站点,JSON 数据类型只能是字符串、数字、对象(JSON 对象)、数组、布尔值或空值。

好的 - 经过一个晚上和半天的沮丧之后 - 我已经得出结论,没有一种“超级干净”的方式来处理这个问题。

Auth0.com上的一篇文章看起来很有希望,创建了Event一个具有多个类属性 ( Date)类(不仅仅是一个接口)——但同样只是基于这些类型在运行时不是真实的(它们只是满足编译器) .

作为解决此问题的一种非超级干净的方法,我使用了另一个 SO 答案,该答案描述了一种相当干净的方法来创建接受部分预填充属性的构造函数。

在我的类中,属性不是简单的数字/字符串等,因此我在我的类的构造函数中构造这些属性:

export class Product {
    public name: string;
    public description: string;
    public viewPublic: boolean;
    public validFrom?: Date;
    public validTo?: Date;
    public _id?: string;

    // https://stackoverflow.com/questions/14142071/typescript-and-field-initializers/37682352#37682352
    /**
     * Constructor to help solidification of this model when a HttpClient.<T>Get() call is made.
     */
    public constructor(init?: Partial<Product>) {
        Object.assign(this, init);

        if (init.validFrom !== null && init.validFrom instanceof Date === false) {
            this.validFrom = new Date(this.validFrom.toString());
        }

        if (init.validTo !== null && init.validTo instanceof Date === false) {
            this.validTo = new Date(this.validTo.toString());
        }
    }
}

(所以你可以构造一个Product类,new Product({ name: "A product", validFrom: "2015-01-02T12:23:42"; })并且你会得到一个Product具有真实Date类型属性的真实对象- 而不仅仅是字符串。

这是我不想要的维护开销水平,但在我的直觉中感觉可以接受。

调用HttpClient并让它Observable<Product>以更“可靠的方式”返回(就像我最初想要的那样)然后变得更简单:

   return this._http
        .get<Product[]>(this.productApiUrl)
        .map(res => new Product(res));

我正在使用 amap()将对象固化为真实的Product.

最后为了处理数组,我在我的 API 服务上添加了一个辅助方法:

/**
 * Solidifies an array of models when the source is JSON via HttpClient.<T>Get() etc
 */
private solidfyAll<T>(objs: T[], type: { new (partial: Partial<T>): T; }): T[] {
    let solidified = [];
    for (let obj of objs) {
        solidified.push(new type(obj));
    }
    return solidified;
}

同样,这可以在map处理时调用Observable<Product[]>

    return this._http
        // type hint/strict the response structure against an interface
        .get<Product[]>(this.productApiUrl)
        .map(res => this.solidfyAll<Product>(res, Product));

您可以从下面的屏幕截图中看到,subscriber现在收到了Date最初期望的真实类型属性:

可观察订阅者中真正分类的对象的屏幕截图

我让它在不需要定义模型的情况下工作。必须在多个地方定义和维护它们很烦人(有解决方案吗?)。

    this.http
      .get<Observable<any>>('https://jsonplaceholder.typicode.com/users')
      .subscribe(
        data => {
          this.data = data;
        },
        error => {
          console.log(error);
        }
      );

现在可以在模板中很好地使用

<div *ngFor="let item of data">
  <p>{{item.name}}</p>
</div>

完整的组件代码

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';

export class ProductListComponent {
  data: any;

  constructor(private http: HttpClient) {
  }

  getdata() {
    this.http
      .get<Observable<any>>('https://jsonplaceholder.typicode.com/users')
      .subscribe(
        data => {
          this.data = data;
        },
        error => {
          console.log(error);
        }
      );
  }
}

版本

"rxjs": "6.6.7",
"@angular/core": "12.0.3",

问题是 JSON 被映射到一个对象,该对象具有指定类型的属性,但没有它的原型,因此没有定义方法。

您可以通过执行以下操作来解决此问题 Object.assign(new MyObject(), data);

我不认为你将能够使用接口,你必须使用一个类(一个具体的实现)。