As @Titian explained the problem arises when what you really need is:
GenericThing<'foo'> | GenericThing<'bar'>
but you have something defined as:
GenericThing<'foo' | 'bar'>
Clearly if you only have two choices like this you can just expand it out yourself, but of course that isn't scalable.
Let's say I have a recursive tree with nodes. This is a simplification:
// different types of nodes
export type NodeType = 'section' | 'page' | 'text' | 'image' | ....;
// node with children
export type OutlineNode<T extends NodeType> = AllowedOutlineNodeTypes> =
{
type: T,
data: NodeDataType[T], // definition not shown
children: OutlineNode<NodeType>[]
}
The types represented by OutlineNode<...>
need to be a discriminated union, which they are because of the type: T
property.
Let's say we have an instance of a node and we iterate through the children:
const node: OutlineNode<'page'> = ....;
node.children.forEach(child =>
{
// check a property that is unique for each possible child type
if (child.type == 'section')
{
// we want child.data to be NodeDataType['section']
// it isn't!
}
})
Clearly in this case I don't want to define children with all possible node types.
An alternative is to 'explode out' the NodeType
where we define children. Unfortunately I couldn't find a way to make this generic because I can't extract out the type name.
Instead you can do the following:
// create type to take 'section' | 'image' and return OutlineNode<'section'> | OutlineNode<'image'>
type ConvertToUnion<T> = T[keyof T];
type OutlineNodeTypeUnion<T extends NodeType> = ConvertToUnion<{ [key in T]: OutlineNode<key> }>;
Then the definition of children
changes to become:
children: OutlineNodeTypeUnion<NodeType>[]
Now when you iterate through the children it's an expanded out definition of all possibilities and the descriminated union type guarding kicks in by itself.
Why not just use a typeguard? 1) You don't really need to. 2) If using something like an angular template you don't want a bazillion calls to your typeguard. This way does in fact allow automatic type narrowing within *ngIf
but unfortunately not ngSwitch