Glimmer component library providing the modern component base class for Ember.js applications
npx @tessl/cli install tessl/npm-glimmer--component@6.1.0@glimmer/component is the modern component library for Ember.js applications, providing a TypeScript-first reactive component architecture based on tracked properties and autotracking. It serves as the lightweight base class that replaced classic Ember components in Ember Octane (3.15+) and newer editions.
npm install @glimmer/componentimport Component from "@glimmer/component";For named imports:
import Component, { type Args, type EmptyObject, type ExpandSignature } from "@glimmer/component";import Component from "@glimmer/component";
// Template-only component (no class needed)
// Create app/templates/components/person-profile.hbs:
// <h1>{{@person.name}}</h1>
// <img src={{@person.avatar}}>
// Class-based component
export default class PersonProfileComponent extends Component {
get displayName() {
let { title, firstName, lastName } = this.args.person;
if (title) {
return `${title} ${lastName}`;
} else {
return `${firstName} ${lastName}`;
}
}
}Template usage:
<PersonProfile @person={{this.currentUser}} />@glimmer/component is built around several key concepts:
@args from parent componentsThe main GlimmerComponent class providing the foundation for all Glimmer components.
/**
* A component is a reusable UI element that consists of a `.hbs` template and an
* optional JavaScript class that defines its behavior.
*/
export default class GlimmerComponent<S = unknown> {
/**
* Constructs a new component and assigns itself the passed properties.
* @param owner - Ember owner/container instance
* @param args - Named arguments object passed from parent component
*/
constructor(owner: Owner, args: Args<S>);
/**
* Named arguments passed to the component from its parent component.
* They can be accessed in JavaScript via `this.args.argumentName` and in the template via `@argumentName`.
*/
readonly args: Readonly<Args<S>>;
/**
* A boolean flag to tell if the component is in the process of destroying.
* This is set to true before `willDestroy` is called.
*/
get isDestroying(): boolean;
/**
* A boolean to tell if the component has been fully destroyed.
* This is set to true after `willDestroy` is called.
*/
get isDestroyed(): boolean;
/**
* Called before the component has been removed from the DOM.
* This lifecycle hook can be used to cleanup the component and any related state.
*/
willDestroy(): void;
}Usage Examples:
import Component from "@glimmer/component";
import { service } from "@ember/service";
// Basic component with computed property
export default class UserCard extends Component {
get fullName() {
return `${this.args.firstName} ${this.args.lastName}`;
}
}
// Component with service injection and lifecycle
export default class AnimatedComponent extends Component {
@service myAnimations;
constructor(owner, args) {
super(owner, args);
if (this.args.fadeIn === true) {
this.myAnimations.register(this, 'fade-in');
}
}
willDestroy() {
super.willDestroy(...arguments);
this.myAnimations.unregister(this);
}
}Type utilities for creating type-safe component signatures with proper argument typing.
/**
* Type utility for extracting named arguments from component signature
*/
type Args<S> = ExpandSignature<S>['Args']['Named'];
/**
* Empty object type with excess property checking
* @internal - Exported for declaration emit only
*/
type EmptyObject = { [Empty]?: true };
/**
* Utility type for expanding component signature shorthand forms
* @internal - Exported for tooling compatibility only
*/
type ExpandSignature<T> = T extends any ? _ExpandSignature<T> : never;Usage Examples:
// Define component signature
interface Signature {
Args: {
firstName: string;
lastName: string;
age?: number;
};
}
export default class UserComponent extends Component<Signature> {
// this.args is now type-safe with firstName, lastName, age properties
get canVote() {
return (this.args.age ?? 0) >= 18;
}
}
// Using the component with type checking
// <UserComponent @firstName="John" @lastName="Doe" @age={{25}} />The args property provides access to all named arguments passed from parent components.
/**
* Named arguments passed to the component from its parent component.
* Automatically tracked for reactivity.
*/
readonly args: Readonly<Args<S>>;Arguments are:
Usage Examples:
// In component class
export default class WelcomeMessage extends Component {
get greeting() {
const timeOfDay = this.args.time === 'morning' ? 'Good morning' : 'Hello';
return `${timeOfDay}, ${this.args.name}!`;
}
}
// In template
// {{this.greeting}}
// Component usage
// <WelcomeMessage @name="Alice" @time="morning" />Component lifecycle management with constructor and destruction hooks.
/**
* Component constructor called when instance is created
* @param owner - Ember owner/container instance
* @param args - Named arguments object
*/
constructor(owner: Owner, args: Args<S>);
/**
* Called before the component has been removed from the DOM.
* Use for cleanup tasks like unregistering event listeners or canceling timers.
*/
willDestroy(): void;Usage Examples:
import Component from "@glimmer/component";
import { service } from "@ember/service";
export default class TimerComponent extends Component {
@service notifications;
timer = null;
constructor(owner, args) {
super(owner, args);
// Initialize component state
if (this.args.autoStart) {
this.startTimer();
}
}
startTimer() {
this.timer = setInterval(() => {
this.notifications.show('Timer tick');
}, 1000);
}
willDestroy() {
super.willDestroy(...arguments);
// Cleanup
if (this.timer) {
clearInterval(this.timer);
}
}
}Properties for tracking component destruction state.
/**
* Boolean flag indicating if component is in the process of destroying.
* Set to true before willDestroy is called.
*/
get isDestroying(): boolean;
/**
* Boolean flag indicating if component has been fully destroyed.
* Set to true after willDestroy is called.
*/
get isDestroyed(): boolean;Usage Examples:
export default class DataComponent extends Component {
async loadData() {
// Avoid async operations if component is being destroyed
if (this.isDestroying) {
return;
}
const data = await fetch('/api/data');
// Check again after async operation
if (!this.isDestroyed) {
this.processData(data);
}
}
}Components work seamlessly with Handlebars templates:
<PersonProfile @person={{this.currentUser}} @mode="detailed" />{{person-profile person=this.currentUser mode="detailed"}}<PersonProfile @person={{this.currentUser}} as |profileData|>
<p>Additional content: {{profileData.summary}}</p>
</PersonProfile><PersonProfile @person={{this.currentUser}}>
<:title as |name|>{{name}} - Custom Title</:title>
<:default as |signature|>{{signature}}</:default>
</PersonProfile>Components should handle errors gracefully:
@glimmer/component integrates deeply with Ember's component system through a custom component manager. The component manager handles:
Components are automatically registered with the component manager when the class is imported, enabling seamless integration with Ember templates.
@glimmer/component relies on these Ember.js ecosystem packages:
@glimmer/env: Environment detection utilities@ember/component: Component system integration@ember/owner: Dependency injection system@ember/runloop: Runloop scheduling for destruction@ember/destroyable: Destruction utilities@embroider/addon-shim: V2 addon compatibility/**
* Utility type for extracting named arguments from component signature
*/
type Args<S> = ExpandSignature<S>['Args']['Named'];
/**
* Empty object type with excess property checking
* @internal - Exported for declaration emit only
*/
type EmptyObject = { [Empty]?: true };
/**
* Utility type for expanding component signature shorthand forms
* @internal - Exported for tooling compatibility only
*/
type ExpandSignature<T> = T extends any ? _ExpandSignature<T> : never;
/**
* Constructor interface for component classes
*/
interface Constructor<T> {
new (owner: unknown, args: Record<string, unknown>): T;
}
/**
* Ember owner/container interface for dependency injection
*/
interface Owner {
// Ember owner methods for service injection and resolution
}