or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

index.mddocs/

@glimmer/component

@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.

Package Information

  • Package Name: @glimmer/component
  • Package Type: npm
  • Language: TypeScript
  • Installation: npm install @glimmer/component
  • Repository: https://github.com/emberjs/ember.js
  • License: MIT

Core Imports

import Component from "@glimmer/component";

For named imports:

import Component, { type Args, type EmptyObject, type ExpandSignature } from "@glimmer/component";

Basic Usage

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}} />

Architecture

@glimmer/component is built around several key concepts:

  • Reactive Component Model: Components automatically re-render when tracked properties change
  • Argument-based API: Data flows down through @args from parent components
  • Lifecycle Hooks: Simple lifecycle with constructor and willDestroy hooks
  • Template Integration: Works seamlessly with Handlebars templates and Ember's rendering engine
  • Type Safety: Full TypeScript support with generic signature types for components
  • Ember Integration: Deep integration with Ember's dependency injection, runloop, and destruction systems

Capabilities

Component Base Class

The 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 Signature Support

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}} />

Component Arguments

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:

  • Readonly: Cannot be mutated directly
  • Tracked: Automatically trigger re-renders when changed by parent
  • Type-safe: When using signature types, provide full TypeScript checking

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" />

Lifecycle Hooks

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);
    }
  }
}

Component State Tracking

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);
    }
  }
}

Template Integration

Components work seamlessly with Handlebars templates:

Angle Bracket Syntax (Recommended)

<PersonProfile @person={{this.currentUser}} @mode="detailed" />

Curly Brace Syntax (Legacy)

{{person-profile person=this.currentUser mode="detailed"}}

Block Form with Yield

<PersonProfile @person={{this.currentUser}} as |profileData|>
  <p>Additional content: {{profileData.summary}}</p>
</PersonProfile>

Named Blocks

<PersonProfile @person={{this.currentUser}}>
  <:title as |name|>{{name}} - Custom Title</:title>
  <:default as |signature|>{{signature}}</:default>
</PersonProfile>

Error Handling

Components should handle errors gracefully:

  • Constructor errors: Validation errors for required arguments
  • Lifecycle errors: Errors during willDestroy cleanup
  • Argument type errors: TypeScript compilation errors for invalid argument types

Component System Integration

@glimmer/component integrates deeply with Ember's component system through a custom component manager. The component manager handles:

  • Component Instantiation: Creates component instances with proper owner and args
  • Lifecycle Management: Schedules destruction using Ember's runloop
  • State Tracking: Manages isDestroying and isDestroyed flags
  • Ember Integration: Ensures proper integration with Ember's rendering engine

Components are automatically registered with the component manager when the class is imported, enabling seamless integration with Ember templates.

Dependencies

@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

Types

/**
 * 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
}