Currently, I'm immersed in a project using Angular 16 where my focus lies on applying a reactive declarative method.
Oftentimes, I find myself working with Observables that emit varying data types - either successful data or an error object. Each return type possesses its distinct interface.
Typically, I define these as union types, but integrating these types within Angular templates proves to be quite challenging.
Here's an illustration:
interface DataSuccess {
value: string;
}
interface DataError {
error: boolean;
message: string;
}
type DataOrError = DataSuccess | DataError | null;
Within my component, I have an Observable of this union type:
// COMPONENT
data$: Observable<DataOrError> = this.myService.getData();
In my Angular template, the goal is to showcase different content based on whether data$ emits a DataSuccess object or a DataError object. However, this results in TypeScript errors indicating that error does not exist on type DataSuccess and value does not exist on type DataError.
// TEMPLATE
<div *ngIf="data$ | async as data">
<!-- Triggers TypeScript error -->
<div *ngIf="data?.error">{{ data.message }}</div>
<!-- Also triggers TypeScript error -->
<div *ngIf="!data?.error">{{ data.value }}</div>
</div>
What are the recommended practices for managing such scenarios in Angular? Is there a way to conduct type-checking within Angular templates without resorting to less type-safe methods (like utilizing the $any() type cast function) or transferring the logic to the component file? Are there any Angular features or TypeScript functionalities that might streamline this process?
I came across some similar queries here, although they were dated, had minimal responses, and lacked satisfactory solutions. It makes me ponder whether things have evolved since then.
Alternatively, I contemplated employing a unified model, meaning rather than having two distinct types and combining them into a union, I would implement something along these lines:
interface DataResponse {
value?: object;
error?: boolean;
message:? string;
}
This approach would alleviate my template inference issue, albeit at the cost of sacrificing some level of type safety. Would adopting this strategy be advisable or could it potentially lead to future complications?