Skip to content

Instantly share code, notes, and snippets.

@jnicklas
Last active November 6, 2025 09:20
Show Gist options
  • Select an option

  • Save jnicklas/afa6a1b1084badc47f57dc5b142b9cb5 to your computer and use it in GitHub Desktop.

Select an option

Save jnicklas/afa6a1b1084badc47f57dc5b142b9cb5 to your computer and use it in GitHub Desktop.
Discriminated unions in GTS components
import Component from '@glimmer/component';
import { join } from '@nullvoxpopuli/ember-composable-helpers';
type SelectArgs = {
options: string[];
} & (
| { multiple: true; selected: string[] }
| { multiple?: false; selected: string }
);
// This component handles both single and multiple selection based on the
// 'multiple' arg using a discriminated union type for the args.
export class SelectComponent extends Component<SelectArgs> {
<template>{{join @options}}</template>
}
// This component just delegates to SelectComponent, passing through all args.
// But implementing this in TypeScript is tricky, even if the args are the exact
// same type!
export class FancySelectComponent extends Component<SelectArgs> {
<template>
I am a fancy select:
<SelectComponent
@options={{@options}}
@selected={{@selected}}
@multiple={{@multiple}}
/>
</template>
}
// TypeScript needs to be able to narrow the type of `@selected`. Here's a brute force way
// of doing this. This is obviously a pretty dumb solution, but this is accepted by TypeScript!
export class FancySelectComponent extends Component<SelectArgs> {
<template>
I am a fancy select:
{{#if @multiple}}
<SelectComponent
@options={{@options}}
@selected={{@selected}}
@multiple={{@multiple}}
/>
{{else}}
<SelectComponent
@options={{@options}}
@selected={{@selected}}
@multiple={{@multiple}}
/>
{{/if}}
</template>
}
// Here's an example using just plain TypeScript without GTS:
export function select(args: SelectArgs) {
// eslint-disable-next-line no-console
console.log(args);
}
// We can do this just fine, but it is not possible to do this with GTS components:
export function fancySelect(args: SelectArgs) {
select(args);
}
// The GTS equivalent of the above function would be more similar to this. Even though
// this looks almost the same, it is a type error in TypeScript, because the relationship
// between multiple and selected is lost.
export function fancySelectGTS({ options, selected, multiple }: SelectArgs) {
select({ options, selected, multiple });
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment