3
votes

I have a TypeScript interfaces:

export interface iA {
  name: string;
}
export interface iB {
  size: number;
}
export interface iX {
  id: number;
  data: iA | iB;
}

Then in one of my Angular component class I'm using interface iX:

// example.component.ts
class ExampleComplnent {
  public item: iX = {
    id: 1,
    data: { name: 'test' }
  }
}
<!-- example.component.html -->
<div>{{ item.data.name }}</div>

This will throw an error: Propery 'name' not exist on type 'iB'.

I know that in a TypeScript file I can use casting like this:

const name = (item.data as iA).name;

But how can I make casting on the HTML side?

I also want to mention that in my code I'm validating that item.data is of type iA, not of type iB. So I'm sure it have the property 'name'.

3
Are your type definitions correct - can item.data either have a name or a size? If so, what do you want to do if the one you render doesn't have a name? Note that's not type casting exactly, it's a type assertion. - jonrsharpe
@jonrsharpe - I've updating my question. Basically I'm validating the type in my code. - Gil Epshtain
Please give a minimal reproducible example. If it's only exposed to the template through the iX interface, the error is correct. - jonrsharpe
My first guess would be to try item.data?.name because when it is the other type, then that property does not exist. - Alexander
That is valid code (Elvis operator/optional chaining) was already available in Angular - Alexander

3 Answers

2
votes

Unfortunately that's not possible. It depends on your implementation what the best solution is. You can use generics:

export interface iA {
  name: string;
}
export interface iB {
  size: number;
}
export interface iX<T extends (iA | iB)> {
  id: number;
  data: T;
}

export class ExampleComponent {
  item: iX<iA>;
}

You can use some method call from the template which asserts the type:

@Component({
  selector: 'my-app',
  template: '{{ getIaData(item)?.name }}',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  item: iX;

  getIaData(ix: iX): iA | undefined {
    return this.isIa(ix.data) ? ix.data : void 0; 
  }

  isIa(data: iA | iB): data is iA {
    return !!(data as iA).name;
  }
}

working example

and I'm sure there are more. But like I said, it depends on where you get the item from and how and where it is defined

3
votes

As @PoulKruijt mentioned, Angular doesn't provide a way to cast using as in the component's template. However, you can consider using $any(), which cast to any. It will suppress the error but it is not type-safe. Refer to this: https://angular.io/guide/template-syntax#the-any-type-cast-function

0
votes

One more solution. I use this for row template

  1. Create util function
export function castTo<T>(): (row) => T {
  return (row) => row as T
}
  1. Call function in component
$row = castTo<YourType>();
  1. Use in template
$row(row).properties