在 Angular 16 模板/类型推断中处理联合类型的最干净方法?

Cleanest way to handle Union Types in Angular 16 Templates / Type inference?

提问人:MikhailRatner 提问时间:9/2/2023 更新时间:9/3/2023 访问量:91

问:

我正在做一个 Angular 16 项目,我使用响应式声明式方法。

通常,我必须处理发出不同类型数据的 Observables,这些数据要么是成功的数据,要么是错误对象。这两种返回类型都有其单独的接口。

我通常将它们定义为联合类型,但我发现在 Angular 模板中处理这些类型很麻烦。

下面是一个例子:

interface DataSuccess {
  value: string;
}

interface DataError {
  error: boolean;
  message: string;
}

type DataOrError = DataSuccess | DataError | null;

在我的组件中,我有一个这种联合类型的 Observable:

// COMPONENT
data$: Observable<DataOrError> = this.myService.getData();

在我的 Angular 模板中,我想根据 data$ 是发出 DataSuccess 对象还是 DataError 对象来显示不同的内容。但是,这会导致 TypeScript 错误,指出 DataSuccess 类型上不存在错误,并且 DataError 类型上不存在值。

// TEMPLATE
<div *ngIf="data$ | async as data">
  <!-- Throws TypeScript error -->
  <div *ngIf="data?.error">{{ data.message }}</div>
  <!-- Also throws TypeScript error -->
  <div *ngIf="!data?.error">{{ data.value }}</div>
</div>

在 Angular 中处理这种情况的最佳实践是什么? 有没有办法在 Angular 模板中执行类型检查,而无需诉诸类型安全性较低的方法(例如使用 $any() 类型转换函数)或将逻辑移动到组件文件?我可能会忽略任何可以简化这一点的 Angular 功能或 TypeScript 功能吗?

我在这里看到了几个类似的问题,但它们很旧,答案很少,答案也不是很令人满意。我想知道,现在情况是否发生了变化。

作为替代方案,我考虑使用统一模型,即与其使用两种不同的类型并将其合并,不如使用以下方法:

interface DataResponse {
value?: object;
error?: boolean;
message:? string;
}

这将解决我的模板推理问题,但是,我会失去一些类型安全性。这是处理此问题的可取方法,还是会为将来可能出现的问题打开大门?

Angular TypeScript rxjs 强制转换 类型错误

评论


答:

1赞 kawai_weebster 9/3/2023 #1

在这里,我创建了一个堆栈闪电战 https://stackblitz.com/edit/stackblitz-starters-9vxc5l?file=src%2Ftest.service.ts

在此,我在您的两个响应中创建了一个名为 type 的新值

 interface DataSuccess {
  type: ResponseType.success;
  value: string;
}

interface DataError {
  type: ResponseType.error;
  error: boolean;
  message: string;
}

enum ResponseType {
  success,
  error,
}

并根据类型将订阅的结果分配给对象。 我认为当您为单个 Observable 提供多种类型时,这将是一种可接受且干净的方式。 此方法使代码的类型更加安全。

评论

0赞 MikhailRatner 9/3/2023
嘿,感谢您的回答以及 StackBlitz 示例!我想这种方法与我在帖子末尾列出的方法非常相似。我可以将它添加到我的所有接口中,使其不可选,并可能将其称为 isResponseError 或 isResponseSuccess。这会是一种合理/可扩展的方法吗?枚举而不是布尔值有什么好处?error: boolean
0赞 MikhailRatner 9/3/2023
此外,使这项工作起作用的部分原因是,您将逻辑移动到组件中,以在类型之间分离(通过有条件地呈现两个额外的变量)。因此,模板不知道联合类型,也不会抛出错误。此外,您还引入了手动订阅/命令性代码,我尽量避免这样做。我有点希望听到一个解决方案,它保持代码的声明性和反应性(不是命令性的),并处理模板内部的联合类型(不向组件添加额外的样板代码)。你知道我的意思?无论如何,谢谢!
1赞 kawai_weebster 9/3/2023
好的,如果你想要一个更具可扩展性的解决方案,你也可以创建一个BaseResponseInterface,添加所有必要的值,并使所有接口扩展这个基础,而不是向所有接口添加布尔值。这将使它更具可扩展性。顺便说一句,你提到你更喜欢 typesafety ,这是使用枚举而不是布尔值的主要好处。更好的编程.pub/...
0赞 MikhailRatner 9/5/2023
谢谢你的信息!我玩了很长一段时间,想出了一个办法:你怎么看?使用泛型将允许将该接口用于所有 API 调用,并且只交换 T。每个响应的 success 或 error 属性均为 null,而另一个响应则包含该值。但是,我不确定这是否是好的实用/可扩展的。interface DataResponse<T> { success: T | null; error: ErrorResponse | null }
0赞 kawai_weebster 9/7/2023
是的,这也有效,您可以尝试以下方法interface BaseResponse { success: boolean; errors: Error[]; stackTrace : StackTrace[], info:string }