Decorators

Ignite-Element provides powerful @Shared and @Isolated decorators for building modular, scalable web components. These decorators streamline integrating state management with rendering logic, allowing you to create maintainable and reusable components.


Enable Decorators in TypeScript

To use decorators in your project, make sure the following options are enabled in your tsconfig.json:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Without these settings, TypeScript will not recognize decorators in your code.


Type Safety with RenderArgs

Ignite-Element provides the RenderArgs type helper to make your components type-safe when using decorators. You’ll need to pass typeof source (e.g., your state machine or store) to RenderArgs to ensure type inference.

Future Plans: We aim to infer types automatically for Ignite-Element decorators in the future, simplifying the setup further.


What Are Decorators

  • @Shared: Creates components that share the same state across all instances. Ideal for global states like progress bars, dashboards, or game stats.

  • @Isolated: Creates components with independent states for each instance. Perfect for localized states like tab navigation, rating systems, or dynamic forms.


Shared Decorator

Overview

The @Shared decorator connects a component to a global state, ensuring all component instances reflect and update the same state.

Example: Progress Bar Component

The following example demonstrates a progress bar that calculates the percentage of completed tasks based on the global state managed by a state machine.

import { Shared, RenderArgs } from "ignite-element";
import { html } from "lit-html";
import taskManagerMachine from "./taskManagerMachine";

@Shared("progress-bar")
export class ProgressBar {
  render({ state }: RenderArgs<typeof taskManagerMachine>) {
    const { tasks } = state.context;
    const completed = tasks.filter((t) => t.completed).length;
    const total = tasks.length;
    const percentage = total > 0 ? (completed / total) * 100 : 0;

    const backgroundStyle =
      percentage === 100
        ? "background: #22c55e;"
        : `background: linear-gradient(
            90deg,
            rgba(34, 197, 94, 1) 0%,
            rgba(251, 191, 36, 1) 50%,
            rgba(209, 213, 219, 0.1) 100%
          );`;

    return html`
      <div class="p-4 bg-blue-100 border rounded-md mt-2 mb-2">
        <h3 class="text-lg font-bold">Progress</h3>
        <div class="w-full bg-gray-200 rounded-full h-4 overflow-hidden">
          <div
            class="h-4 rounded-full transition-all duration-700 ease-out"
            style="width: ${percentage}%; ${backgroundStyle}"
          ></div>
        </div>
        <p class="mt-2">${completed}/${total} tasks completed</p>
      </div>
    `;
  }
}

Isolated Decorator

Overview

The @Isolated decorator connects a component to a state that is independent for each instance. This ensures that multiple component instances can coexist with unique behaviors and states.

Example 2: Rating Component

The RatingItem component tracks its rating state independently for each instance.

import { Isolated } from "ignite-element";
import { RenderArgs } from "ignite-element";
import ratingMachine from "./ratingMachine";

@Isolated("rating-item")
export class RatingItem {
  render({ state, send }: RenderArgs<typeof ratingMachine>) {
    const { rating, maxRating } = state.context;

    return html`
      <div class="p-4 bg-white border rounded-md">
        <h3 class="font-bold mb-2">Rate this item:</h3>
        <div class="flex space-x-2">
          ${Array.from({ length: maxRating }, (_, i) =>
            html`
              <button
                class="text-xl ${rating > i ? 'text-yellow-500' : 'text-gray-300'}"
                @click=${() => send({ type: "RATE", value: i + 1 })}
              >

              </button>
            `
          )}
        </div>
        <p class="mt-2 text-sm text-gray-600">
          You rated this ${rating}/${maxRating}.
        </p>
      </div>
    `;
  }
}

Best Practices

  1. Centralize Logic:

    • Use state machines or stores to manage all data and interactions.

  2. Choose the Right Decorator:

    • Use @Shared for global states (e.g., dashboards, progress bars).

    • Use @Isolated for independent states (e.g., tabs, ratings).

  3. Dynamic Contexts:

    • Configure state machines dynamically for greater flexibility.

  4. Encapsulate Rendering:

    • Keep rendering logic focused and delegate state management to the source.

Last updated