Problem
Angular doesn't do type narrowing based on conditions in *ngIf
or *ngSwitchCase
(angular/angular#20780).
And, making it worse, there is no way to write a type assertion directly in a template.
With strictInputTypes
check turned on it can result in a number of false positive compile time errors.
A common workaround is to have a method in your component that does type assertion, and call this method in a template.
Naive approach
@Component({
template: `
<component-a
*ngIf="item.type === 'type-A'"
[inputOfTypeA]="asTypeA(item)"
>
`
})
export class ComponentAorB {
asTypeA = (item: A | B): A => item as A; // note a type assertion here
}
It's a lot of boilerplate: you need a separate method for each subtype you need to narrow down to. And it's not type safe. Nothing prevents you from accidentally doing smth like this:
<component-a
*ngIf="item.type === 'type-B'"
[inputOfTypeA]="asTypeA(item)"
>
Adding type safety
An evolution of this method could be to combine the runtime check with type assertion in a single method in a component:
@Component({
template: `
<component-a
*ngIf="maybeAsTypeA(item) as itemA"
[inputOfTypeA]="itemA"
>
`
})
export class ComponentAorB {
maybeAsTypeA = (item: A | B): A | undefined =>
item.type === 'type-A' ? item : undefined
}
Here we have improved type safety - see how there is no type assertions at all. But still a lot of boilerplate, as we need a separate method for each subtype of the union type.
Fighting boilerplate
With a bit of generic magic we could extract this boilerplate into a reusable function - see below.
Related links:
- Implementation of
Subtype
generic type is based on this SO answer. - Typescript playground with
Subtype
andasSubtypeFactory
.