Glint Types
ComponentLike
, HelperLike
and ModifierLike
ComponentLike
, HelperLike
and ModifierLike
While we often work in terms of specific implementations of components, helpers and modifiers, when we're using e.g. MyComponent
in a template, it doesn't matter whether MyComponent
is a template-only component, or a subclass of @glimmer/component
or is a completely different object with a custom component manager.
To account for this, the @glint/template
package provides a set of more general types: the ComponentLike
, HelperLike
and ModifierLike
types describe any value that is usable as the respective type of entity in a template.
For example, in Ember all of the following values are ComponentLike
:
a subclass of
@glimmer/component
a subclass of
@ember/component
the return value of
templateOnlyComponent()
from@ember/component/template-only
a
<template>
expression in a.gts
filethe result of a
{{component ...}}
expression in a template
These types each accept signatures in the same format that the base classes for components and helpers/modifiers do.
WithBoundArgs
and WithBoundPositionals
WithBoundArgs
and WithBoundPositionals
When you yield a "contextual component" (or helper or modifier), you need some way to declare the type of that value in the signature of the yielding component.
The return value from {{component}}
component isn't the actual SomeBanner
class—it won't have e.g. any of SomeBanner
's static members, and it also no longer requires a @kind
arg, since a default value has been set as part of the (component)
invocation.
We could use ComponentLike
to describe the type of this value:
However, that's quite a lot of boilerplate to essentially express "it's like SomeBanner
except kind
is already set". Instead, you can use the WithBoundArgs
type to express the same thing:
If you had pre-bound multiple named args, you could union them together with the |
type operator, e.g. 'kind' | 'title'
.
Similarly, when working with a component/helper/modifier where you're pre-binding positional arguments, you can use WithBoundPositionals
to indicate to downstream consumers that those arguments are already set:
Where WithBoundArgs
accepts the names of the pre-bound arguments, WithBoundPositionals
accepts the number of positional arguments that are pre-bound, since binding a positional argument with {{component}}
/{{modifier}}
/{{helper}}
sets that argument in a way that downstream users can't override.
Advanced Types Usage
From Glint's perspective, what makes a value usable as a component is being typed as a constructor for a value type that matches the instance type of ComponentLike
. The same is true of helpers with HelperLike
and modifiers with ModifierLike
.
While this may seem like a negligible detail, making use of this fact can allow authors with a good handle on TypeScript's type system to pull off some very flexible "tricks" when working with Glint.
Custom Glint Entities
Ember (and the underlying Glimmer VM) has a notion of managers that allow authors to define custom values that act as components, helpers or modifiers when used in a template. Glint can't know how these custom entities will work, but by using ComponentLike
/HelperLike
/ModifierLike
, you can explain to the typechecker how they function in a template.
For example, if you had a custom DOM-less "fetcher component" base class, you could use TypeScript declaration merging to tell Glint that its instance type extended InstanceType<ComponentLike<S>>
, where S
is an appropriate component signature based on how your custom component works.
This is a fairly contrived example, and in most circumstances it would be simpler to use a standard base class like @glimmer/component
, but nevertheless the option exists.
Note: this declaration merging technique using InstanceType<ComponentLike<...>>
is exactly how Glint's own 1st-party environment packages like @glint/environment-ember-loose
set up the template-aware types for @glimmer/component
, @ember/component/helper
, etc.
Type Parameters
When defining a class-based component, modifier or helper, you have a natural place to introduce any type parameters you may need. For example:
However, if you aren't working with a concrete base type and can only say that your value is, for instance, some kind of ComponentLike
, then TypeScript no longer offers you a place to introduce a type parameter into scope:
Since what matters is the instance type, however, it is possible to define MyEach
using just ComponentLike
and slightly more type machinery:
This shouldn't be a tool you frequently find the need to reach for, but it can be useful on occasion when working with complex declarations.
Last updated