Angular - type guard not narrowing types

70 Views Asked by At

I have interfaces

Angular Typescript Class

interface A_DTO {
  type: string, 
  commonProperty: string, 
  uniquePropertyA: string, 
  etc..
}

interface B_DTO {
  type: string, 
  commonProperty: string, 
  uniquePropertyB: string,
  etc...
}

type AnyDTO = A_DTO | B_DTO

I have an object, fetched from an API. When it is fetched, it immediately gets cast to A_DTO or B_DTO, by reading the 'type' property. But after that, it then it gets saved to a service, for storage, where it gets saved to a single variable, but of typ AnyDTO (I call that service variable with the components I work with - casting back to AnyDTO, doesn't cause any properties to be lost, so I'm happy)

Angular Template

But, in a component, I have some template code,

@if(object.type == "Type_A") {
 // do something
 // I can do object.commonProperty
 // but I cannot access object.uniquePropertyA
} @ else { object.type == "Type_B") {
 // do something
 // I can do object.commonProperty
 // but I cannot access object.uniquePropertyB
}

Note, above, object gets read as type, AnyDTO = A_DTO | B_DTO

Angular Typescript Class

I tried creating a type guard on the interface, in the typescript class code, e.g.

  protected isTypeA(object: any): object is A_DTO {
    return object?.Type === "Type_A";
  }, 

Angular Template

Then

@if(isTypeA(object)) {
 // do something
 // I can do object.commonProperty
 // but I still cannot access object.uniquePropertyA...
} @ else { object.type == "Type_B") {
 // do something
 // but I cannot access object.uniquePropertyB
}

Even with the typeguard being called in the template, inside the @if, 'object' still gets treated as type: A_DTO | B_DTO. Despite what I read on the internet, type narrowing does not happen. So can only access the common properties from 'object'.

I also tried to explicity type cast in the template, using things like (object as A_DTO).uniquePropertyA, but that doesn't work in the Angular template area

Any ideas on a dynamic solution, (that ideally does not involve create separate variables for each subtype in the Typescript class)?

Cheers,

ST

1

There are 1 best solutions below

5
Andriy On BEST ANSWER

try to rewrite you AnyDTO to:

type AnyDTO = { type: 'Type_A' } & A_DTO | { type: 'Type_B' } & B_DTO;

this way TS should treat AnyDTO with Type_A and Type_B differently.

Please note that you use types union |, which converts your interfaces to types, so, theoretically you could use type and not interface for A_DTO and B_DTO.

STACKBLITZ