将 rxjs 订阅包装在扩展方法中以返回新模型

问题描述 投票:0回答:1

我继承了一个 API,它为每个端点响应返回一个自定义

Response
包装器对象。我正在构建一个 Angular 应用程序来从此 API 获取数据,并在我的 Typescript 中定义了类似的
Response
类型。

export class Response<T> {
    code: number;
    data: T;
    message?: string;
}

当我进行 API 调用时,我会收到 JSON 格式的相应响应。

import { HttpClient } from '@angular/common/http';

this.http.post<Response<Data>>(url, payload).subscribe({  // Type defined here (Data)
   next: (response) => {
       if (response.code === 200) {
           this.data = response.data;  // This is not typed, returns Object, but everything works
       }
   }
})

我注意到的第一件事是,当响应从 Angular http 调用返回时,它实际上并没有被输入。这只是一个

Object
,我无法强制执行其他扩展方法来验证响应是否成功返回或解析以进行进一步的错误处理。

我想出的一个解决方案是将此调用包装在我可以自己键入的内容中,以获取要使用的实际类型对象并处理任何错误(

Response
返回 null 或 BadDataException 等)

export function typeSubscribe<T>(
    this: Observable<Response<T>>,
    next?: (value: T) => void,
    error?: (error: T) => void,
    complete?: () => void
): Subscription {

    let newNext: (value: T) => void = next;

    if (next) {
        newNext = (value: T): void => {
            value = buildReponse<T>(value);
            if (value.code !== 200) {
                error(value.message);
                return;
            }
            next(value.data);
        };
    }

    const sub = this.subscribe({ next: newNext, error, complete });
    return sub;
}

function buildReponse<T>(body: any): Response<T> {
    const response = new Response<T>();
    if (!body) {
        response.code = 500;
        response.message = 'Response is empty';
        response.data = null;
        return response;
    }
    response.code = body.code;
    response.message = body.message;
    response.data = map<T>(body.data, body.data.constructor);   // body.data is Object or Array, whatever comes back from API
    return response;
}

// This function just returns empty Object {}
function map<T>(values: any, ctor: new () => T): T {
    const instance = new ctor();
    return Object.keys(instance).reduce((acc: any, key: string) => {
        acc[key] = values[key];
        return acc;
    }, {}) as T;
 }

意图像这样使用它

this.http.post<Response<Data>>(url, payload).typeSubscribe((response) => {
   this.data = response;  // this.data will now be fully typed as `Data` and handled appropriately
})

我的问题:

  1. 似乎没有办法让
    typeSubscribe
    知道
    T
    是什么。尽管在 IDE 编译器中的某个地方,IntelliSense 在每一步都知道它,但它似乎在运行时不知道它。如何在代码中的此时在 Typescript 中实例化此类型(来自 Http 响应)。
  2. 如果我可以处理
    Response
    对象并仅在成功时传递
    data
    ,那就太好了。是否可以扩展订阅以具有一种类型(
    Response<T>
    )的可观察对象并仅返回
    T
    ?订阅似乎需要 Observable 类型
    this
    来匹配从
    next
    返回的内容。可以把它们做成不同的类型吗?

此时我并不担心正确的错误处理或日志记录,只是想让打字系统正常工作并理解此时的订阅。

angular typescript rxjs typescript-generics
1个回答
0
投票

我认为你正在增加复杂性来解决简单的事情!如果未识别,请添加类型,至于为什么会发生这种情况,我无法检查,除非它在 stackblitz 上复制,否则可能有多种原因,请检查下面的 stackblitz 工作正常,请尝试复制发出并分享回来。

临时修复

import { HttpClient } from '@angular/common/http';

this.http.post<Response<Data>>(url, payload).subscribe({  // Type defined here (Data)
   next: (response: Data) => {
       if (response.code === 200) {
           this.data = response.data;  // This is not typed, returns Object, but everything works
       }
   }
})

ts - 问题无法重现!

import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import {
  HttpClient,
  HttpClientModule,
  provideHttpClient,
} from '@angular/common/http';
import 'zone.js';

export class Response<T> {
  code!: number;
  data!: T;
  message?: string;
}

export interface Data {
  id: number;
  title: string;
  userId: number;
}

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [HttpClientModule],
  template: `
    <h1>Hello from {{ name }}!</h1>
    <a target="_blank" href="https://angular.dev/overview">
      Learn more about Angular
    </a>
  `,
})
export class App {
  name = 'Angular';
  data!: Data;

  constructor(private http: HttpClient) {}

  ngOnInit() {
    this.http
      .post<Response<Data>>('https://dummyjson.com/posts/add', {
        title: 'I am in love with someone.',
        userId: 5,
      })
      .subscribe({
        // Type defined here (Data)
        next: (response) => {
          console.log(response);
          if (response.code === 200) {
            this.data = response!.data; // This is not typed, returns Object, but everything works
          }
        },
      });
  }
}

bootstrapApplication(App, {
  providers: [provideHttpClient()],
});

堆栈闪电战

© www.soinside.com 2019 - 2024. All rights reserved.