1

I have a problem with typescript inheritance and property reference in Angular HTML template and couldn't solve it for a long time. (if it's question duplication, sorry for that, I couldn't find question like this).

So I have a parent interface, let it call DummyParent with some properties.

export interface DummyParent {
    dummy1?: string;
    dummy2?: number;
}

And I have two children of this interface.

export interface DummyChild1 extends DummyParent {
    foo1?: string;
    bar1: number;
}
export interface DummyChild2 extends DummyParent {
    foo2?: string;
    bar2: number;
}

And I have an Angular component, which get an input and it's type can be a copy of all of these interfaces.

@Input() dummy!: DummyParent | DummyChild1 | DummyChild2;

But when I have a reference in HTML template for a DummyChild property, I got an error. For example:

<ng-container *ngIf="dummy.foo1"></ng-container> 
Error: Property 'foo1' does not exist on type 'DummyParent'.

Based on this error, seems that dummy property is just a DummyParent object, and I can't reference as a DummyChild1 or DummyChild2 object. My question is that, how should I instantiate @Input() dummy to be a typeof DummyParent OR DummyChild1 OR DummyChild2. I know that I can solve the problem with using object type 'any' or casting object in HTML template like this <ng-container *ngIf="$any(dummy).foo1"> but i try to avoid using these solutions. Thanks a lot for any answers.

2 Answers 2

3

This is how TS unions work:

export interface DummyParent {
    dummy1?: string;
    dummy2?: number;
}

export interface DummyChild1 extends DummyParent {
    foo1?: string;
    bar1: number;
}
export interface DummyChild2 extends DummyParent {
    foo2?: string;
    bar2: number;
}

type Union = DummyParent | DummyChild1 | DummyChild2

declare var union: Union;

// only dumm1 and dummy2 are available
union.dummy1
union.dummy2

union allows you to use common properties from all dummy types. Because each type extends DummyParent you are allowed to use only properties from DummyParent.

In order to make it safe, you should use custom type guards:

export interface DummyParent {
    dummy1?: string;
    dummy2?: number;
}

export interface DummyChild1 extends DummyParent {
    foo1?: string;
    bar1: number;
}
export interface DummyChild2 extends DummyParent {
    foo2?: string;
    bar2: number;
}

type Union = DummyParent | DummyChild1 | DummyChild2

declare var union: Union;

// only dumm1 and dummy2 are available
union.dummy1
union.dummy2


const isOne = (union: Union): union is DummyChild1 => 'bar1' in union
const isTwo = (union: Union): union is DummyChild2 => 'bar2' in union

if(isOne(union)){
    union.bar1 // ok
}

if(isTwo(union)){
    union.bar2 // ok
}

Then, I assume you can do smth like that:

<ng-container *ngIf="isTwo(union)">
 // do smth with union.bar2
</ng-container>

P.S. I'm not 100% sure that in operator is the best solution for such kind of typeguard. You can also consider this:

const hasProperty = <Obj, Prop extends string>(obj: Obj, prop: Prop)
  : obj is Obj & Record<Prop, unknown> =>
  Object.prototype.hasOwnProperty.call(obj, prop);
Sign up to request clarification or add additional context in comments.

Comments

1

My question is that, how should I instantiate @Input() dummy to be a typeof DummyParent OR DummyChild1 OR DummyChild2.

That's what you're currently doing. If you're saying something is either type A, type B or type C you can only acces the properties that exist on ALL THREE types. Otherwise, what would be the point of explicit typing if you could still access non-existent properties?

And since in your cases B and C inherit after A, it will be only properties of type A. If you want to access properties of type A, you have narrow the type to A (e.g. by using type guard).

You can read more about it in the official TypeScript docs here.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.