For the complete documentation index, see llms.txt. This page is also available as Markdown.

Glint Types

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 file

  • the 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

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.

{{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:

Custom Elements / Web Components

By default, Glint knows nothing about custom elements: <my-element> is typed as a plain Element, and any attributes are accepted.

To teach Glint about a custom element, register it in one (or both) of two global interfaces:

  • GlintCustomElementTagNameMap — maps the tag name to the element's instance type (mirroring the DOM's own HTMLElementTagNameMap). This is what gives the element a real type for modifiers, ...attributes, and hover info.

  • GlintCustomElementAttributesMap — maps the tag name to the attributes the element accepts. Attributes are registered separately because TypeScript has no way to derive "settable attributes" from an element class (methods and readonly DOM properties would leak in).

With both registered:

Hovering a registered element's tag name shows its element type, and go-to-definition on the tag name resolves to its registry entry.

The two registries are independent:

  • Registering only the element type gives you a typed element (for modifiers and ...attributes) while leaving attributes unchecked.

  • Registering only attributes checks them while leaving the element typed as Element.

  • Tag names registered in neither registry behave as before: Element type, arbitrary attributes accepted.

If you already augment the DOM's standard HTMLElementTagNameMap for your custom elements (as is conventional in e.g. Lit), Glint picks the element type up from there too — GlintCustomElementTagNameMap exists for projects that prefer not to extend the DOM's own registry.

Last updated