Glint Types
ComponentLike, HelperLike and ModifierLike
ComponentLike, HelperLike and ModifierLikeWhile 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/componenta subclass of
@ember/componentthe return value of
templateOnlyComponent()from@ember/component/template-onlya
<template>expression in a.gtsfilethe 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 WithBoundPositionalsWhen 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.
{{yield (hash banner=(component "some-banner" kind="warning"))}}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:
import { ComponentLike } from '@glint/template';
import { SomeBannerSignature } from './some-banner';
interface MyComponentSignature {
Blocks: {
default: [{
banner: ComponentLike<{
Element: SomeBannerSignature['Element'];
Blocks: SomeBannerSignature['Blocks'];
Args:
Omit<SomeBannerSignature['Args'], 'kind'>
& { kind?: SomeBannerSignature['Args']['kind'] };
}>;
}];
};
}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 similar to how libraries set up 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.
Custom Element Attributes
HTML allows for unspecified attributes, but by default Glint will tell you when an attribute is not defined as being a part of the definition for that element.
To add an allowed attribute to a particular element, you'll want to use declaration merging to add the attribute and its valid values to the type checker
example:
allows use of
If you wish to add attributes to all elements, you may declaration merge the GlobalAttributes interface:
Last updated